hyperbrowser 1.2.3__tar.gz → 1.4.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.
Files changed (114) hide show
  1. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/PKG-INFO +2 -1
  2. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/README.md +1 -0
  3. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/sandbox.py +235 -37
  4. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_transport.py +4 -0
  5. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/volume.py +8 -0
  6. hyperbrowser-1.4.0/hyperbrowser/client/managers/sandboxes/dockerfile_analysis.py +293 -0
  7. hyperbrowser-1.4.0/hyperbrowser/client/managers/sandboxes/dockerignore.py +254 -0
  8. hyperbrowser-1.4.0/hyperbrowser/client/managers/sandboxes/image_build.py +1484 -0
  9. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/sandbox.py +228 -36
  10. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_transport.py +4 -0
  11. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/volume.py +8 -0
  12. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/__init__.py +22 -1
  13. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/sandbox.py +81 -2
  14. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/volume.py +6 -0
  15. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/sandbox_common.py +64 -1
  16. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/transport/async_transport.py +43 -14
  17. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/transport/sync.py +45 -14
  18. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/__init__.py +12 -0
  19. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/sandbox.py +64 -1
  20. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/pyproject.toml +1 -1
  21. hyperbrowser-1.2.3/hyperbrowser/client/managers/sandboxes/image_build.py +0 -375
  22. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/LICENSE +0 -0
  23. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/__init__.py +0 -0
  24. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/_request.py +0 -0
  25. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/async_client.py +0 -0
  26. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/base.py +0 -0
  27. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/agents/__init__.py +0 -0
  28. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/agents/browser_use.py +0 -0
  29. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/agents/claude_computer_use.py +0 -0
  30. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/agents/cua.py +0 -0
  31. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/agents/gemini_computer_use.py +0 -0
  32. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/agents/grok_computer_use.py +0 -0
  33. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/agents/hyper_agent.py +0 -0
  34. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/computer_action.py +0 -0
  35. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/crawl.py +0 -0
  36. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/extension.py +0 -0
  37. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/extract.py +0 -0
  38. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/profile.py +0 -0
  39. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/sandboxes/__init__.py +0 -0
  40. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_files.py +0 -0
  41. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_processes.py +0 -0
  42. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_terminal.py +0 -0
  43. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/scrape.py +0 -0
  44. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/session.py +0 -0
  45. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/team.py +0 -0
  46. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/web/__init__.py +0 -0
  47. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/web/batch_fetch.py +0 -0
  48. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/async_manager/web/crawl.py +0 -0
  49. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sandboxes/__init__.py +0 -0
  50. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sandboxes/shared.py +0 -0
  51. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/agents/__init__.py +0 -0
  52. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/agents/browser_use.py +0 -0
  53. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/agents/claude_computer_use.py +0 -0
  54. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/agents/cua.py +0 -0
  55. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/agents/gemini_computer_use.py +0 -0
  56. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/agents/grok_computer_use.py +0 -0
  57. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/agents/hyper_agent.py +0 -0
  58. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/computer_action.py +0 -0
  59. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/crawl.py +0 -0
  60. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/extension.py +0 -0
  61. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/extract.py +0 -0
  62. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/profile.py +0 -0
  63. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/sandboxes/__init__.py +0 -0
  64. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_files.py +0 -0
  65. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_processes.py +0 -0
  66. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_terminal.py +0 -0
  67. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/scrape.py +0 -0
  68. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/session.py +0 -0
  69. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/team.py +0 -0
  70. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/web/__init__.py +0 -0
  71. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/web/batch_fetch.py +0 -0
  72. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/managers/sync_manager/web/crawl.py +0 -0
  73. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/client/sync.py +0 -0
  74. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/config.py +0 -0
  75. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/exceptions.py +0 -0
  76. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/_parsers.py +0 -0
  77. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/agents/browser_use.py +0 -0
  78. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/agents/claude_computer_use.py +0 -0
  79. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/agents/cua.py +0 -0
  80. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/agents/gemini_computer_use.py +0 -0
  81. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/agents/grok_computer_use.py +0 -0
  82. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/agents/hyper_agent.py +0 -0
  83. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/computer_action.py +0 -0
  84. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/consts.py +0 -0
  85. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/crawl.py +0 -0
  86. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/extension.py +0 -0
  87. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/extract.py +0 -0
  88. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/profile.py +0 -0
  89. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/scrape.py +0 -0
  90. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/session.py +0 -0
  91. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/team.py +0 -0
  92. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/web/batch_fetch.py +0 -0
  93. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/web/branding.py +0 -0
  94. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/web/common.py +0 -0
  95. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/web/crawl.py +0 -0
  96. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/web/fetch.py +0 -0
  97. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/models/web/search.py +0 -0
  98. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/py.typed +0 -0
  99. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/tools/__init__.py +0 -0
  100. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/tools/anthropic.py +0 -0
  101. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/tools/openai.py +0 -0
  102. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/tools/schema.py +0 -0
  103. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/transport/base.py +0 -0
  104. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/_json.py +0 -0
  105. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/agents.py +0 -0
  106. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/computer_action.py +0 -0
  107. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/crawl.py +0 -0
  108. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/extension.py +0 -0
  109. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/extract.py +0 -0
  110. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/profile.py +0 -0
  111. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/scrape.py +0 -0
  112. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/session.py +0 -0
  113. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/volume.py +0 -0
  114. {hyperbrowser-1.2.3 → hyperbrowser-1.4.0}/hyperbrowser/types/web.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperbrowser
3
- Version: 1.2.3
3
+ Version: 1.4.0
4
4
  Summary: Python SDK for hyperbrowser
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -250,6 +250,7 @@ sandbox = client.sandboxes.create(
250
250
  )
251
251
 
252
252
  sandbox.stop()
253
+ client.volumes.delete(same_volume.id)
253
254
  client.close()
254
255
  ```
255
256
 
@@ -223,6 +223,7 @@ sandbox = client.sandboxes.create(
223
223
  )
224
224
 
225
225
  sandbox.stop()
226
+ client.volumes.delete(same_volume.id)
226
227
  client.close()
227
228
  ```
228
229
 
@@ -9,12 +9,14 @@ from ....models.sandbox import (
9
9
  CompleteSandboxImageBuildParams,
10
10
  CreateSandboxParams,
11
11
  CreateSandboxImageBuildParams,
12
+ ReuseSandboxDockerImageParams,
12
13
  SandboxDetail,
13
14
  SandboxExecParams,
14
15
  SandboxExposeParams,
15
16
  SandboxExposeResult,
16
17
  SandboxImageBuild,
17
18
  SandboxImageBuildCreateResult,
19
+ SandboxDockerImageReuseResult,
18
20
  SandboxImageBuildListParams,
19
21
  SandboxImageBuildListResponse,
20
22
  SandboxImageListParams,
@@ -39,6 +41,7 @@ from ....models.session import BasicResponse
39
41
  from ....types import (
40
42
  CompleteSandboxImageBuildParams as CompleteSandboxImageBuildParamsDict,
41
43
  CreateSandboxImageBuildParams as CreateSandboxImageBuildParamsDict,
44
+ ReuseSandboxDockerImageParams as ReuseSandboxDockerImageParamsDict,
42
45
  CreateSandboxParams as CreateSandboxParamsDict,
43
46
  SandboxExecParams as SandboxExecParamsDict,
44
47
  SandboxExposeParams as SandboxExposeParamsDict,
@@ -54,8 +57,11 @@ from ....types import (
54
57
  from ....sandbox_common import (
55
58
  RuntimeConnection,
56
59
  ensure_response_ok,
60
+ get_retry_delay_seconds,
57
61
  normalize_network_error,
62
+ request_context,
58
63
  parse_json_response,
64
+ should_retry_get,
59
65
  )
60
66
  from ..sandboxes.shared import (
61
67
  _build_sandbox_exposed_url,
@@ -67,9 +73,12 @@ from ..sandboxes.image_build import (
67
73
  build_docker_image_from_dockerfile,
68
74
  is_terminal_image_build_status,
69
75
  make_temp_docker_tag,
70
- package_docker_image,
76
+ merge_image_init,
77
+ package_docker_build_context_manifest,
78
+ package_docker_image_manifest,
79
+ prepare_docker_image_manifest_source,
71
80
  remove_docker_image,
72
- upload_image_build_artifact,
81
+ upload_missing_image_build_artifacts,
73
82
  )
74
83
  from .sandboxes.sandbox_files import (
75
84
  DEFAULT_WATCH_TIMEOUT_MS,
@@ -494,6 +503,20 @@ class SandboxManager:
494
503
  payload = await self._request("GET", f"/images/builds/{build_id}")
495
504
  return SandboxImageBuild(**payload["build"])
496
505
 
506
+ async def reuse_docker_image(
507
+ self,
508
+ params: Union[
509
+ ReuseSandboxDockerImageParamsDict,
510
+ ReuseSandboxDockerImageParams,
511
+ ],
512
+ ) -> SandboxDockerImageReuseResult:
513
+ payload = await self._request(
514
+ "POST",
515
+ "/images/builds/reuse",
516
+ data=dump_request(params, ReuseSandboxDockerImageParams),
517
+ )
518
+ return SandboxDockerImageReuseResult(**payload)
519
+
497
520
  async def complete_image_build(
498
521
  self,
499
522
  build_id: str,
@@ -566,18 +589,158 @@ class SandboxManager:
566
589
  temp_dir: Optional[str] = None,
567
590
  upload_timeout: Optional[float] = None,
568
591
  ) -> SandboxImageBuild:
569
- artifact = await _run_blocking(
570
- package_docker_image,
592
+ source = await _run_blocking(
593
+ prepare_docker_image_manifest_source,
571
594
  docker_image,
572
595
  platform=platform,
596
+ )
597
+ try:
598
+ explicit_image_init = (
599
+ coerce_request(image_init, SandboxImageInit, name="image_init")
600
+ if image_init is not None
601
+ else None
602
+ )
603
+ normalized_image_init = merge_image_init(
604
+ source.image_init,
605
+ explicit_image_init,
606
+ )
607
+ normalized_image_config_user = (
608
+ image_config_user
609
+ if image_config_user is not None
610
+ else source.image_config_user
611
+ )
612
+ try:
613
+ reused = await self.reuse_docker_image(
614
+ ReuseSandboxDockerImageParams(
615
+ image_name=image_name,
616
+ source_image_digest=source.image_digest,
617
+ source_platform=platform,
618
+ image_config_user=normalized_image_config_user,
619
+ image_init=normalized_image_init,
620
+ )
621
+ )
622
+ except HyperbrowserError as error:
623
+ if error.status_code != 404:
624
+ raise
625
+ reused = None
626
+ if reused is not None and reused.hit:
627
+ if reused.build is None:
628
+ raise RuntimeError(
629
+ "exact image cache response is missing its completed build"
630
+ )
631
+ return reused.build
632
+
633
+ packaged = await _run_blocking(
634
+ package_docker_image_manifest,
635
+ docker_image,
636
+ source.image_digest,
637
+ source.config,
638
+ platform=platform,
639
+ temp_dir=temp_dir,
640
+ )
641
+ build_id = None
642
+ build_started = False
643
+ try:
644
+ artifact = packaged.artifact
645
+ create_result = await self.create_image_build(
646
+ CreateSandboxImageBuildParams(
647
+ image_name=image_name,
648
+ input_sha256=artifact.sha256_hex,
649
+ input_size_bytes=artifact.size_bytes,
650
+ input_format=artifact.input_format,
651
+ source_platform=artifact.source_platform,
652
+ image_config_user=normalized_image_config_user,
653
+ image_init=normalized_image_init,
654
+ docker_image_manifest=packaged.manifest,
655
+ )
656
+ )
657
+ build_id = create_result.build.id
658
+ await _run_blocking(
659
+ upload_missing_image_build_artifacts,
660
+ create_result.uploads,
661
+ packaged.layers,
662
+ label="Docker image layer",
663
+ timeout=upload_timeout,
664
+ )
665
+ build = await self._complete_image_build_resilient(
666
+ build_id,
667
+ artifact,
668
+ )
669
+ build_started = True
670
+ if wait:
671
+ return await self.wait_for_image_build(
672
+ build.id,
673
+ poll_interval=poll_interval,
674
+ timeout=wait_timeout,
675
+ )
676
+ return build
677
+ except Exception:
678
+ if build_id is not None and not build_started:
679
+ try:
680
+ await self.cancel_image_build(build_id)
681
+ except Exception:
682
+ pass
683
+ raise
684
+ finally:
685
+ await _run_blocking(packaged.cleanup)
686
+ finally:
687
+ await _run_blocking(source.cleanup)
688
+
689
+ async def _complete_image_build_resilient(
690
+ self,
691
+ build_id: str,
692
+ artifact,
693
+ ) -> SandboxImageBuild:
694
+ params = CompleteSandboxImageBuildParams(
695
+ input_sha256=artifact.sha256_hex,
696
+ input_size_bytes=artifact.size_bytes,
697
+ input_format=artifact.input_format,
698
+ )
699
+ try:
700
+ return await self.complete_image_build(build_id, params)
701
+ except HyperbrowserError as exc:
702
+ if exc.status_code != 409:
703
+ raise
704
+ if "already in progress" in str(exc).lower():
705
+ return await self.get_image_build(build_id)
706
+ current = await self.get_image_build(build_id)
707
+ if current.status not in ("awaiting_upload", "upload_verified"):
708
+ raise
709
+ await asyncio.sleep(2)
710
+ return await self.complete_image_build(build_id, params)
711
+
712
+ async def _build_image_from_remote_dockerfile(
713
+ self,
714
+ *,
715
+ context_path,
716
+ image_name: str,
717
+ dockerfile,
718
+ platform: str,
719
+ remote_full_context: bool,
720
+ image_init: Optional[Union[SandboxImageInitDict, SandboxImageInit]],
721
+ image_config_user: Optional[str],
722
+ wait: bool,
723
+ poll_interval: float,
724
+ wait_timeout: Optional[float],
725
+ temp_dir: Optional[str],
726
+ upload_timeout: Optional[float],
727
+ ) -> SandboxImageBuild:
728
+ packaged = await _run_blocking(
729
+ package_docker_build_context_manifest,
730
+ context_path,
731
+ dockerfile=dockerfile,
732
+ force_full_context=remote_full_context,
573
733
  temp_dir=temp_dir,
574
734
  )
735
+ build_id = None
736
+ build_started = False
575
737
  try:
576
738
  normalized_image_init = (
577
739
  coerce_request(image_init, SandboxImageInit, name="image_init")
578
740
  if image_init is not None
579
- else artifact.image_init
741
+ else None
580
742
  )
743
+ artifact = packaged.artifact
581
744
  create_result = await self.create_image_build(
582
745
  CreateSandboxImageBuildParams(
583
746
  image_name=image_name,
@@ -585,28 +748,22 @@ class SandboxManager:
585
748
  input_size_bytes=artifact.size_bytes,
586
749
  input_format=artifact.input_format,
587
750
  source_platform=artifact.source_platform,
588
- image_config_user=(
589
- image_config_user
590
- if image_config_user is not None
591
- else artifact.image_config_user
592
- ),
751
+ dockerfile_path=packaged.manifest.dockerfile_path,
752
+ image_config_user=image_config_user,
593
753
  image_init=normalized_image_init,
754
+ context_manifest=packaged.manifest,
594
755
  )
595
756
  )
757
+ build_id = create_result.build.id
596
758
  await _run_blocking(
597
- upload_image_build_artifact,
598
- create_result.upload,
599
- artifact.path,
759
+ upload_missing_image_build_artifacts,
760
+ create_result.uploads,
761
+ packaged.bundles,
762
+ label="build context bundle",
600
763
  timeout=upload_timeout,
601
764
  )
602
- build = await self.complete_image_build(
603
- create_result.build.id,
604
- CompleteSandboxImageBuildParams(
605
- input_sha256=artifact.sha256_hex,
606
- input_size_bytes=artifact.size_bytes,
607
- input_format=artifact.input_format,
608
- ),
609
- )
765
+ build = await self._complete_image_build_resilient(build_id, artifact)
766
+ build_started = True
610
767
  if wait:
611
768
  return await self.wait_for_image_build(
612
769
  build.id,
@@ -614,8 +771,15 @@ class SandboxManager:
614
771
  timeout=wait_timeout,
615
772
  )
616
773
  return build
774
+ except Exception:
775
+ if build_id is not None and not build_started:
776
+ try:
777
+ await self.cancel_image_build(build_id)
778
+ except Exception:
779
+ pass
780
+ raise
617
781
  finally:
618
- artifact.cleanup()
782
+ await _run_blocking(packaged.cleanup)
619
783
 
620
784
  async def build_image_from_dockerfile(
621
785
  self,
@@ -623,6 +787,8 @@ class SandboxManager:
623
787
  context_path,
624
788
  image_name: str,
625
789
  dockerfile="Dockerfile",
790
+ remote: bool = True,
791
+ remote_full_context: bool = False,
626
792
  docker_tag: Optional[str] = None,
627
793
  platform: str = IMAGE_BUILD_SOURCE_PLATFORM,
628
794
  build_args: Optional[Dict[str, str]] = None,
@@ -634,6 +800,26 @@ class SandboxManager:
634
800
  temp_dir: Optional[str] = None,
635
801
  upload_timeout: Optional[float] = None,
636
802
  ) -> SandboxImageBuild:
803
+ if remote:
804
+ if docker_tag is not None or build_args:
805
+ raise ValueError(
806
+ "docker_tag and build_args require remote=False; remote "
807
+ "Dockerfile builds send the build context to Hyperbrowser"
808
+ )
809
+ return await self._build_image_from_remote_dockerfile(
810
+ context_path=context_path,
811
+ image_name=image_name,
812
+ dockerfile=dockerfile,
813
+ platform=platform,
814
+ remote_full_context=remote_full_context,
815
+ image_init=image_init,
816
+ image_config_user=image_config_user,
817
+ wait=wait,
818
+ poll_interval=poll_interval,
819
+ wait_timeout=wait_timeout,
820
+ temp_dir=temp_dir,
821
+ upload_timeout=upload_timeout,
822
+ )
637
823
  tag = docker_tag or make_temp_docker_tag()
638
824
  remove_tag = docker_tag is None
639
825
  try:
@@ -759,19 +945,31 @@ class SandboxManager:
759
945
  params: Optional[Dict[str, object]] = None,
760
946
  data: Optional[Dict[str, object]] = None,
761
947
  ):
762
- try:
763
- response = await self._client.transport.client.request(
764
- method,
765
- self._client._build_url(path),
766
- params={k: v for k, v in (params or {}).items() if v is not None},
767
- json=data,
768
- )
769
- except BaseException as error:
770
- raise normalize_network_error(
771
- error,
772
- "control",
773
- "Unknown error occurred",
774
- )
948
+ failed_attempt = 1
949
+ while True:
950
+ try:
951
+ response = await self._client.transport.client.request(
952
+ method,
953
+ self._client._build_url(path),
954
+ params={
955
+ key: value
956
+ for key, value in (params or {}).items()
957
+ if value is not None
958
+ },
959
+ json=data,
960
+ )
961
+ ensure_response_ok(response, "control")
962
+ except BaseException as cause:
963
+ error = normalize_network_error(
964
+ cause,
965
+ "control",
966
+ "Unknown error occurred",
967
+ request_context(method, path),
968
+ )
969
+ if not should_retry_get(method, error, failed_attempt):
970
+ raise error
971
+ await asyncio.sleep(get_retry_delay_seconds(failed_attempt))
972
+ failed_attempt += 1
973
+ continue
775
974
 
776
- ensure_response_ok(response, "control")
777
- return parse_json_response(response, "control")
975
+ return parse_json_response(response, "control")
@@ -9,6 +9,7 @@ from .....sandbox_common import (
9
9
  ensure_response_ok,
10
10
  normalize_network_error,
11
11
  parse_json_response,
12
+ request_context,
12
13
  resolve_runtime_transport_target,
13
14
  )
14
15
  from ...sandboxes.shared import _build_query_path, _is_replayable_http_content
@@ -275,6 +276,7 @@ class RuntimeTransport:
275
276
  error,
276
277
  "runtime",
277
278
  "Unknown runtime request error",
279
+ request_context(method, path),
278
280
  )
279
281
 
280
282
  await response.aread()
@@ -309,6 +311,7 @@ class RuntimeTransport:
309
311
  error,
310
312
  "runtime",
311
313
  "Unknown runtime request error",
314
+ request_context(method, path),
312
315
  )
313
316
 
314
317
  async def _send_stream(
@@ -341,4 +344,5 @@ class RuntimeTransport:
341
344
  error,
342
345
  "runtime",
343
346
  "Unknown runtime request error",
347
+ request_context("GET", path),
344
348
  )
@@ -4,6 +4,7 @@ from hyperbrowser.client._request import dump_request
4
4
  from hyperbrowser.models.volume import (
5
5
  CreateVolumeParams,
6
6
  Volume,
7
+ VolumeDeleteResult,
7
8
  VolumeListParams,
8
9
  VolumeListResponse,
9
10
  )
@@ -43,3 +44,10 @@ class VolumeManager:
43
44
  self._client._build_url(f"/volume/{volume_id}")
44
45
  )
45
46
  return Volume(**response.data)
47
+
48
+ async def delete(self, volume_id: str) -> VolumeDeleteResult:
49
+ """Delete a volume by id or name. Ambiguous names and active mounts return 409."""
50
+ response = await self._client.transport.delete(
51
+ self._client._build_url(f"/volume/{volume_id}")
52
+ )
53
+ return VolumeDeleteResult(**response.data)