blaxel 0.2.26rc119__py3-none-any.whl → 0.2.27__py3-none-any.whl

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 (115) hide show
  1. blaxel/__init__.py +3 -5
  2. blaxel/core/__init__.py +9 -1
  3. blaxel/core/client/api/{privateclusters/create_private_cluster.py → images/cleanup_images.py} +31 -25
  4. blaxel/core/client/models/__init__.py +2 -4
  5. blaxel/core/client/models/agent.py +1 -0
  6. blaxel/core/client/models/agent_spec.py +1 -24
  7. blaxel/core/client/models/billable_time_metric.py +1 -0
  8. blaxel/core/{sandbox/client/models/find_match.py → client/models/cleanup_images_response_200.py} +19 -19
  9. blaxel/core/client/models/configuration.py +1 -0
  10. blaxel/core/client/models/core_event.py +9 -0
  11. blaxel/core/client/models/core_spec.py +1 -24
  12. blaxel/core/client/models/core_spec_configurations.py +1 -0
  13. blaxel/core/client/models/create_job_execution_request.py +1 -0
  14. blaxel/core/client/models/create_job_execution_response.py +1 -0
  15. blaxel/core/client/models/custom_domain.py +1 -0
  16. blaxel/core/client/models/custom_domain_metadata.py +1 -0
  17. blaxel/core/client/models/custom_domain_spec.py +1 -0
  18. blaxel/core/client/models/delete_volume_template_version_response_200.py +1 -0
  19. blaxel/core/client/models/entrypoint.py +1 -0
  20. blaxel/core/client/models/form.py +1 -0
  21. blaxel/core/client/models/function.py +1 -0
  22. blaxel/core/client/models/function_spec.py +1 -24
  23. blaxel/core/client/models/image.py +1 -0
  24. blaxel/core/client/models/image_spec.py +1 -0
  25. blaxel/core/client/models/integration.py +1 -0
  26. blaxel/core/client/models/integration_connection.py +1 -0
  27. blaxel/core/client/models/integration_connection_spec.py +1 -0
  28. blaxel/core/client/models/integration_endpoint.py +1 -0
  29. blaxel/core/client/models/integration_endpoints.py +2 -0
  30. blaxel/core/client/models/job.py +1 -0
  31. blaxel/core/client/models/job_execution.py +1 -0
  32. blaxel/core/client/models/job_execution_spec.py +1 -0
  33. blaxel/core/client/models/job_execution_task.py +1 -0
  34. blaxel/core/client/models/job_metrics.py +1 -0
  35. blaxel/core/client/models/job_spec.py +1 -24
  36. blaxel/core/client/models/jobs_network_chart.py +1 -0
  37. blaxel/core/client/models/jobs_success_failed_chart.py +1 -0
  38. blaxel/core/client/models/latency_metric.py +1 -0
  39. blaxel/core/client/models/location_response.py +1 -0
  40. blaxel/core/client/models/mcp_definition.py +1 -0
  41. blaxel/core/client/models/metadata.py +1 -0
  42. blaxel/core/client/models/metrics.py +34 -0
  43. blaxel/core/client/models/model.py +1 -0
  44. blaxel/core/client/models/model_spec.py +1 -24
  45. blaxel/core/client/models/pending_invitation_accept.py +1 -0
  46. blaxel/core/client/models/pending_invitation_render.py +1 -0
  47. blaxel/core/client/models/policy.py +1 -0
  48. blaxel/core/client/models/policy_spec.py +1 -0
  49. blaxel/core/client/models/preview.py +1 -0
  50. blaxel/core/client/models/preview_spec.py +1 -0
  51. blaxel/core/client/models/preview_token.py +1 -0
  52. blaxel/core/client/models/public_ips.py +1 -0
  53. blaxel/core/client/models/request_duration_over_time_metrics.py +1 -0
  54. blaxel/core/client/models/request_total_by_origin_metric.py +1 -0
  55. blaxel/core/client/models/request_total_metric.py +1 -0
  56. blaxel/core/client/models/resource_metrics.py +1 -0
  57. blaxel/core/client/models/revision_configuration.py +9 -0
  58. blaxel/core/client/models/runtime.py +1 -0
  59. blaxel/core/client/models/sandbox.py +1 -0
  60. blaxel/core/client/models/sandbox_definition.py +1 -0
  61. blaxel/core/client/models/sandbox_lifecycle.py +1 -0
  62. blaxel/core/client/models/sandbox_spec.py +1 -24
  63. blaxel/core/client/models/serverless_config.py +1 -0
  64. blaxel/core/client/models/start_sandbox.py +1 -0
  65. blaxel/core/client/models/stop_sandbox.py +1 -0
  66. blaxel/core/client/models/store_agent.py +1 -0
  67. blaxel/core/client/models/store_configuration.py +1 -0
  68. blaxel/core/client/models/template.py +1 -0
  69. blaxel/core/client/models/time_to_first_token_over_time_metrics.py +1 -0
  70. blaxel/core/client/models/token_rate_metrics.py +1 -0
  71. blaxel/core/client/models/trigger.py +10 -0
  72. blaxel/core/client/models/trigger_configuration.py +28 -0
  73. blaxel/core/client/models/volume.py +1 -0
  74. blaxel/core/client/models/volume_template.py +1 -0
  75. blaxel/core/client/models/websocket_channel.py +9 -0
  76. blaxel/core/client/models/workspace.py +1 -0
  77. blaxel/core/client/response_interceptor.py +0 -1
  78. blaxel/core/common/__init__.py +11 -2
  79. blaxel/core/common/autoload.py +1 -85
  80. blaxel/core/common/settings.py +56 -16
  81. blaxel/core/common/webhook.py +187 -0
  82. blaxel/core/jobs/__init__.py +352 -3
  83. blaxel/core/sandbox/client/models/__init__.py +0 -16
  84. blaxel/core/sandbox/default/action.py +10 -27
  85. blaxel/core/sandbox/default/filesystem.py +47 -185
  86. blaxel/core/sandbox/default/interpreter.py +55 -62
  87. blaxel/core/sandbox/default/process.py +46 -66
  88. {blaxel-0.2.26rc119.dist-info → blaxel-0.2.27.dist-info}/METADATA +2 -3
  89. {blaxel-0.2.26rc119.dist-info → blaxel-0.2.27.dist-info}/RECORD +91 -114
  90. blaxel/core/client/api/privateclusters/__init__.py +0 -0
  91. blaxel/core/client/api/privateclusters/delete_private_cluster.py +0 -152
  92. blaxel/core/client/api/privateclusters/get_private_cluster.py +0 -155
  93. blaxel/core/client/api/privateclusters/get_private_cluster_health.py +0 -97
  94. blaxel/core/client/api/privateclusters/list_private_clusters.py +0 -136
  95. blaxel/core/client/api/privateclusters/update_private_cluster.py +0 -152
  96. blaxel/core/client/api/privateclusters/update_private_cluster_health.py +0 -97
  97. blaxel/core/client/models/model_private_cluster.py +0 -79
  98. blaxel/core/client/models/private_cluster.py +0 -183
  99. blaxel/core/sandbox/client/api/filesystem/delete_filesystem_tree_path.py +0 -188
  100. blaxel/core/sandbox/client/api/filesystem/get_filesystem_content_search_path.py +0 -265
  101. blaxel/core/sandbox/client/api/filesystem/get_filesystem_find_path.py +0 -248
  102. blaxel/core/sandbox/client/api/filesystem/get_filesystem_search_path.py +0 -237
  103. blaxel/core/sandbox/client/api/filesystem/get_filesystem_tree_path.py +0 -197
  104. blaxel/core/sandbox/client/api/filesystem/put_filesystem_tree_path.py +0 -223
  105. blaxel/core/sandbox/client/api/websocket/__init__.py +0 -0
  106. blaxel/core/sandbox/client/api/websocket/get_ws.py +0 -81
  107. blaxel/core/sandbox/client/models/content_search_match.py +0 -98
  108. blaxel/core/sandbox/client/models/content_search_response.py +0 -97
  109. blaxel/core/sandbox/client/models/find_response.py +0 -88
  110. blaxel/core/sandbox/client/models/fuzzy_search_match.py +0 -78
  111. blaxel/core/sandbox/client/models/fuzzy_search_response.py +0 -88
  112. blaxel/core/sandbox/client/models/tree_request.py +0 -76
  113. blaxel/core/sandbox/client/models/tree_request_files.py +0 -49
  114. {blaxel-0.2.26rc119.dist-info → blaxel-0.2.27.dist-info}/WHEEL +0 -0
  115. {blaxel-0.2.26rc119.dist-info → blaxel-0.2.27.dist-info}/licenses/LICENSE +0 -0
@@ -2,8 +2,6 @@
2
2
 
3
3
  from .apply_edit_request import ApplyEditRequest
4
4
  from .apply_edit_response import ApplyEditResponse
5
- from .content_search_match import ContentSearchMatch
6
- from .content_search_response import ContentSearchResponse
7
5
  from .delete_network_process_pid_monitor_response_200 import (
8
6
  DeleteNetworkProcessPidMonitorResponse200,
9
7
  )
@@ -15,10 +13,6 @@ from .file_with_content import FileWithContent
15
13
  from .filesystem_multipart_upload import FilesystemMultipartUpload
16
14
  from .filesystem_multipart_upload_parts import FilesystemMultipartUploadParts
17
15
  from .filesystem_uploaded_part import FilesystemUploadedPart
18
- from .find_match import FindMatch
19
- from .find_response import FindResponse
20
- from .fuzzy_search_match import FuzzySearchMatch
21
- from .fuzzy_search_response import FuzzySearchResponse
22
16
  from .get_network_process_pid_ports_response_200 import GetNetworkProcessPidPortsResponse200
23
17
  from .multipart_complete_request import MultipartCompleteRequest
24
18
  from .multipart_initiate_request import MultipartInitiateRequest
@@ -39,15 +33,11 @@ from .ranked_file import RankedFile
39
33
  from .reranking_response import RerankingResponse
40
34
  from .subdirectory import Subdirectory
41
35
  from .success_response import SuccessResponse
42
- from .tree_request import TreeRequest
43
- from .tree_request_files import TreeRequestFiles
44
36
  from .welcome_response import WelcomeResponse
45
37
 
46
38
  __all__ = (
47
39
  "ApplyEditRequest",
48
40
  "ApplyEditResponse",
49
- "ContentSearchMatch",
50
- "ContentSearchResponse",
51
41
  "DeleteNetworkProcessPidMonitorResponse200",
52
42
  "Directory",
53
43
  "ErrorResponse",
@@ -57,10 +47,6 @@ __all__ = (
57
47
  "FilesystemMultipartUploadParts",
58
48
  "FilesystemUploadedPart",
59
49
  "FileWithContent",
60
- "FindMatch",
61
- "FindResponse",
62
- "FuzzySearchMatch",
63
- "FuzzySearchResponse",
64
50
  "GetNetworkProcessPidPortsResponse200",
65
51
  "MultipartCompleteRequest",
66
52
  "MultipartInitiateRequest",
@@ -81,7 +67,5 @@ __all__ = (
81
67
  "RerankingResponse",
82
68
  "Subdirectory",
83
69
  "SuccessResponse",
84
- "TreeRequest",
85
- "TreeRequestFiles",
86
70
  "WelcomeResponse",
87
71
  )
@@ -1,7 +1,5 @@
1
1
 
2
- import os
3
2
  import httpx
4
- from contextlib import asynccontextmanager
5
3
 
6
4
  from ...common.internal import get_forced_url, get_global_unique_hash
7
5
  from ...common.settings import settings
@@ -11,7 +9,6 @@ from ..types import ResponseError, SandboxConfiguration
11
9
  class SandboxAction:
12
10
  def __init__(self, sandbox_config: SandboxConfiguration):
13
11
  self.sandbox_config = sandbox_config
14
- self._client: httpx.AsyncClient | None = None
15
12
 
16
13
  @property
17
14
  def name(self) -> str:
@@ -46,8 +43,8 @@ class SandboxAction:
46
43
  url = self.forced_url
47
44
  return url[:-1] if url.endswith("/") else url
48
45
  # Uncomment when mk3 is fully available
49
- if settings.run_internal_hostname:
50
- return self.internal_url
46
+ # if settings.run_internal_hostname:
47
+ # return self.internal_url
51
48
  return self.external_url
52
49
 
53
50
  @property
@@ -57,29 +54,15 @@ class SandboxAction:
57
54
  return None
58
55
 
59
56
  def get_client(self) -> httpx.AsyncClient:
60
- """Get persistent HTTP client for this sandbox instance."""
61
- if self._client is None:
62
- base_url = self.sandbox_config.force_url or self.url
63
-
64
- # Build headers
65
- if self.sandbox_config.force_url:
66
- headers = self.sandbox_config.headers.copy()
67
- else:
68
- headers = {**settings.headers, **self.sandbox_config.headers}
69
-
70
- # Add X-Blaxel-Workspace header if BL_WORKSPACE env var is present
71
- bl_workspace = os.environ.get("BL_WORKSPACE")
72
- if bl_workspace:
73
- headers["X-Blaxel-Workspace"] = bl_workspace
74
-
75
- self._client = httpx.AsyncClient(
76
- base_url=base_url,
77
- headers=headers,
78
- http2=False,
79
- limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
80
- timeout=httpx.Timeout(300.0, connect=10.0),
57
+ if self.sandbox_config.force_url:
58
+ return httpx.AsyncClient(
59
+ base_url=self.sandbox_config.force_url, headers=self.sandbox_config.headers
81
60
  )
82
- return self._client
61
+ # Create a new client instance each time to avoid "Cannot open a client instance more than once" error
62
+ return httpx.AsyncClient(
63
+ base_url=self.url,
64
+ headers={**settings.headers, **self.sandbox_config.headers},
65
+ )
83
66
 
84
67
  def handle_response_error(self, response: httpx.Response):
85
68
  if not response.is_success:
@@ -31,14 +31,10 @@ class SandboxFileSystem(SandboxAction):
31
31
  path = self.format_path(path)
32
32
  body = FileRequest(is_directory=True, permissions=permissions)
33
33
 
34
- client = self.get_client()
35
- response = await client.put(f"/filesystem/{path}", json=body.to_dict())
36
- try:
37
- data = json.loads(await response.aread())
34
+ async with self.get_client() as client_instance:
35
+ response = await client_instance.put(f"/filesystem/{path}", json=body.to_dict())
38
36
  self.handle_response_error(response)
39
- return SuccessResponse.from_dict(data)
40
- finally:
41
- await response.aclose()
37
+ return SuccessResponse.from_dict(response.json())
42
38
 
43
39
  async def write(self, path: str, content: str) -> SuccessResponse:
44
40
  path = self.format_path(path)
@@ -54,14 +50,10 @@ class SandboxFileSystem(SandboxAction):
54
50
  # Use regular upload for small files
55
51
  body = FileRequest(content=content)
56
52
 
57
- client = self.get_client()
58
- response = await client.put(f"/filesystem/{path}", json=body.to_dict())
59
- try:
60
- data = json.loads(await response.aread())
53
+ async with self.get_client() as client_instance:
54
+ response = await client_instance.put(f"/filesystem/{path}", json=body.to_dict())
61
55
  self.handle_response_error(response)
62
- return SuccessResponse.from_dict(data)
63
- finally:
64
- await response.aclose()
56
+ return SuccessResponse.from_dict(response.json())
65
57
 
66
58
  async def write_binary(self, path: str, content: Union[bytes, bytearray, str]) -> SuccessResponse:
67
59
  """Write binary content to a file.
@@ -101,16 +93,13 @@ class SandboxFileSystem(SandboxAction):
101
93
  url = f"{self.url}/filesystem/{path}"
102
94
  headers = {**settings.headers, **self.sandbox_config.headers}
103
95
 
104
- client = self.get_client()
105
- response = await client.put(url, files=files, data=data, headers=headers)
106
- try:
107
- content_bytes = await response.aread()
96
+ async with self.get_client() as client_instance:
97
+ response = await client_instance.put(url, files=files, data=data, headers=headers)
98
+
108
99
  if not response.is_success:
109
- error_text = content_bytes.decode('utf-8', errors='ignore')
110
- raise Exception(f"Failed to write binary: {response.status_code} {error_text}")
111
- return SuccessResponse.from_dict(json.loads(content_bytes))
112
- finally:
113
- await response.aclose()
100
+ raise Exception(f"Failed to write binary: {response.status_code} {response.text}")
101
+
102
+ return SuccessResponse.from_dict(response.json())
114
103
 
115
104
  async def write_tree(
116
105
  self,
@@ -126,32 +115,26 @@ class SandboxFileSystem(SandboxAction):
126
115
 
127
116
  path = destination_path or ""
128
117
 
129
- client = self.get_client()
130
- response = await client.put(
131
- f"/filesystem/tree/{path}",
132
- json={"files": files_dict},
133
- headers={"Content-Type": "application/json"},
134
- )
135
- try:
136
- data = json.loads(await response.aread())
118
+ async with self.get_client() as client_instance:
119
+ response = await client_instance.put(
120
+ f"/filesystem/tree/{path}",
121
+ json={"files": files_dict},
122
+ headers={"Content-Type": "application/json"},
123
+ )
137
124
  self.handle_response_error(response)
138
- return Directory.from_dict(data)
139
- finally:
140
- await response.aclose()
125
+ return Directory.from_dict(response.json())
141
126
 
142
127
  async def read(self, path: str) -> str:
143
128
  path = self.format_path(path)
144
129
 
145
- client = self.get_client()
146
- response = await client.get(f"/filesystem/{path}")
147
- try:
148
- data = json.loads(await response.aread())
130
+ async with self.get_client() as client_instance:
131
+ response = await client_instance.get(f"/filesystem/{path}")
149
132
  self.handle_response_error(response)
133
+
134
+ data = response.json()
150
135
  if "content" in data:
151
136
  return data["content"]
152
137
  raise Exception("Unsupported file type")
153
- finally:
154
- await response.aclose()
155
138
 
156
139
  async def read_binary(self, path: str) -> bytes:
157
140
  """Read binary content from a file.
@@ -171,14 +154,10 @@ class SandboxFileSystem(SandboxAction):
171
154
  "Accept": "application/octet-stream",
172
155
  }
173
156
 
174
- client = self.get_client()
175
- response = await client.get(url, headers=headers)
176
- try:
177
- content = await response.aread()
157
+ async with self.get_client() as client_instance:
158
+ response = await client_instance.get(url, headers=headers)
178
159
  self.handle_response_error(response)
179
- return content
180
- finally:
181
- await response.aclose()
160
+ return response.content
182
161
 
183
162
  async def download(self, src: str, destination_path: str, mode: int = 0o644) -> None:
184
163
  """Download a file from the sandbox to the local filesystem.
@@ -196,125 +175,23 @@ class SandboxFileSystem(SandboxAction):
196
175
  async def rm(self, path: str, recursive: bool = False) -> SuccessResponse:
197
176
  path = self.format_path(path)
198
177
 
199
- client = self.get_client()
200
- params = {"recursive": "true"} if recursive else {}
201
- response = await client.delete(f"/filesystem/{path}", params=params)
202
- try:
203
- data = json.loads(await response.aread())
178
+ async with self.get_client() as client_instance:
179
+ params = {"recursive": "true"} if recursive else {}
180
+ response = await client_instance.delete(f"/filesystem/{path}", params=params)
204
181
  self.handle_response_error(response)
205
- return SuccessResponse.from_dict(data)
206
- finally:
207
- await response.aclose()
182
+ return SuccessResponse.from_dict(response.json())
208
183
 
209
184
  async def ls(self, path: str) -> Directory:
210
185
  path = self.format_path(path)
211
186
 
212
- client = self.get_client()
213
- response = await client.get(f"/filesystem/{path}")
214
- try:
215
- data = json.loads(await response.aread())
187
+ async with self.get_client() as client_instance:
188
+ response = await client_instance.get(f"/filesystem/{path}")
216
189
  self.handle_response_error(response)
190
+
191
+ data = response.json()
217
192
  if not ("files" in data or "subdirectories" in data):
218
193
  raise Exception('{"error": "Directory not found"}')
219
194
  return Directory.from_dict(data)
220
- finally:
221
- await response.aclose()
222
-
223
- async def find(
224
- self,
225
- path: str,
226
- type: str | None = None,
227
- patterns: List[str] | None = None,
228
- max_results: int | None = None,
229
- exclude_dirs: List[str] | None = None,
230
- exclude_hidden: bool | None = None,
231
- ):
232
- """Find files and directories.
233
-
234
- Args:
235
- path: Path to search in
236
- type: Type of search ('file' or 'directory')
237
- patterns: File patterns to include (e.g., ['*.py', '*.json'])
238
- max_results: Maximum number of results to return
239
- exclude_dirs: Directory names to skip
240
- exclude_hidden: Exclude hidden files and directories
241
-
242
- Returns:
243
- FindResponse with matching files/directories
244
- """
245
- path = self.format_path(path)
246
-
247
- params = {}
248
- if type is not None:
249
- params['type'] = type
250
- if patterns is not None and len(patterns) > 0:
251
- params['patterns'] = ','.join(patterns)
252
- if max_results is not None:
253
- params['maxResults'] = max_results
254
- if exclude_dirs is not None and len(exclude_dirs) > 0:
255
- params['excludeDirs'] = ','.join(exclude_dirs)
256
- if exclude_hidden is not None:
257
- params['excludeHidden'] = exclude_hidden
258
-
259
- client = self.get_client()
260
- response = await client.get(f"/filesystem-find/{path}", params=params)
261
- try:
262
- data = json.loads(await response.aread())
263
- self.handle_response_error(response)
264
-
265
- from ..client.models.find_response import FindResponse
266
- return FindResponse.from_dict(data)
267
- finally:
268
- await response.aclose()
269
-
270
- async def grep(
271
- self,
272
- query: str,
273
- path: str = "/",
274
- case_sensitive: bool | None = None,
275
- context_lines: int | None = None,
276
- max_results: int | None = None,
277
- file_pattern: str | None = None,
278
- exclude_dirs: List[str] | None = None,
279
- ):
280
- """Search for text content inside files using ripgrep.
281
-
282
- Args:
283
- query: Text to search for
284
- path: Directory path to search in
285
- case_sensitive: Case sensitive search (default: false)
286
- context_lines: Number of context lines to include (default: 0)
287
- max_results: Maximum number of results to return (default: 100)
288
- file_pattern: File pattern to include (e.g., '*.py')
289
- exclude_dirs: Directory names to skip
290
-
291
- Returns:
292
- ContentSearchResponse with matching lines
293
- """
294
- path = self.format_path(path)
295
-
296
- params = {'query': query}
297
- if case_sensitive is not None:
298
- params['caseSensitive'] = case_sensitive
299
- if context_lines is not None:
300
- params['contextLines'] = context_lines
301
- if max_results is not None:
302
- params['maxResults'] = max_results
303
- if file_pattern is not None:
304
- params['filePattern'] = file_pattern
305
- if exclude_dirs is not None and len(exclude_dirs) > 0:
306
- params['excludeDirs'] = ','.join(exclude_dirs)
307
-
308
- client = self.get_client()
309
- response = await client.get(f"/filesystem-content-search/{path}", params=params)
310
- try:
311
- data = json.loads(await response.aread())
312
- self.handle_response_error(response)
313
-
314
- from ..client.models.content_search_response import ContentSearchResponse
315
- return ContentSearchResponse.from_dict(data)
316
- finally:
317
- await response.aclose()
318
195
 
319
196
  async def cp(self, source: str, destination: str, max_wait: int = 180000) -> CopyResponse:
320
197
  """Copy files or directories using the cp command.
@@ -448,14 +325,10 @@ class SandboxFileSystem(SandboxAction):
448
325
  headers = {**settings.headers, **self.sandbox_config.headers}
449
326
  body = {"permissions": permissions}
450
327
 
451
- client = self.get_client()
452
- response = await client.post(url, json=body, headers=headers)
453
- try:
454
- data = json.loads(await response.aread())
328
+ async with self.get_client() as client_instance:
329
+ response = await client_instance.post(url, json=body, headers=headers)
455
330
  self.handle_response_error(response)
456
- return data
457
- finally:
458
- await response.aclose()
331
+ return response.json()
459
332
 
460
333
  async def _upload_part(
461
334
  self, upload_id: str, part_number: int, data: bytes
@@ -468,16 +341,12 @@ class SandboxFileSystem(SandboxAction):
468
341
  # Prepare multipart form data with the file chunk
469
342
  files = {"file": ("part", io.BytesIO(data), "application/octet-stream")}
470
343
 
471
- client = self.get_client()
472
- response = await client.put(
473
- url, files=files, params=params, headers=headers
474
- )
475
- try:
476
- data = json.loads(await response.aread())
344
+ async with self.get_client() as client_instance:
345
+ response = await client_instance.put(
346
+ url, files=files, params=params, headers=headers
347
+ )
477
348
  self.handle_response_error(response)
478
- return data
479
- finally:
480
- await response.aclose()
349
+ return response.json()
481
350
 
482
351
  async def _complete_multipart_upload(
483
352
  self, upload_id: str, parts: List[Dict[str, Any]]
@@ -487,28 +356,21 @@ class SandboxFileSystem(SandboxAction):
487
356
  headers = {**settings.headers, **self.sandbox_config.headers}
488
357
  body = {"parts": parts}
489
358
 
490
- client = self.get_client()
491
- response = await client.post(url, json=body, headers=headers)
492
- try:
493
- data = json.loads(await response.aread())
359
+ async with self.get_client() as client_instance:
360
+ response = await client_instance.post(url, json=body, headers=headers)
494
361
  self.handle_response_error(response)
495
- return SuccessResponse.from_dict(data)
496
- finally:
497
- await response.aclose()
362
+ return SuccessResponse.from_dict(response.json())
498
363
 
499
364
  async def _abort_multipart_upload(self, upload_id: str) -> None:
500
365
  """Abort a multipart upload and clean up all parts."""
501
366
  url = f"{self.url}/filesystem-multipart/{upload_id}/abort"
502
367
  headers = {**settings.headers, **self.sandbox_config.headers}
503
368
 
504
- client = self.get_client()
505
- response = await client.delete(url, headers=headers)
506
- try:
369
+ async with self.get_client() as client_instance:
370
+ response = await client_instance.delete(url, headers=headers)
507
371
  # Don't raise error if abort fails - we want to throw the original error
508
372
  if not response.is_success:
509
373
  print(f"Warning: Failed to abort multipart upload: {response.status_code}")
510
- finally:
511
- await response.aclose()
512
374
 
513
375
  async def _upload_with_multipart(
514
376
  self, path: str, data: bytes, permissions: str = "0644"
@@ -198,55 +198,55 @@ class CodeInterpreter(SandboxInstance):
198
198
 
199
199
  execution = CodeInterpreter.Execution()
200
200
 
201
- client = self.process.get_client()
202
- timeout_cfg = httpx.Timeout(
203
- connect=connect_timeout, read=read_timeout, write=write_timeout, pool=pool_timeout
204
- )
205
- async with client.stream(
206
- "POST",
207
- "/port/8888/execute",
208
- json=body,
209
- timeout=timeout_cfg,
210
- ) as response:
211
- if response.status_code >= 400:
212
- try:
213
- body_text = await response.aread()
214
- body_text = body_text.decode(errors="ignore")
215
- except Exception:
216
- body_text = "<unavailable>"
217
- req = getattr(response, "request", None)
218
- method = getattr(req, "method", "UNKNOWN") if req else "UNKNOWN"
219
- url = str(getattr(req, "url", "UNKNOWN")) if req else "UNKNOWN"
220
- reason = getattr(response, "reason_phrase", "")
221
- details = (
222
- "Execution failed\n"
223
- f"- method: {method}\n- url: {url}\n- status: {response.status_code} {reason}\n"
224
- f"- response-headers: {dict(response.headers)}\n- body:\n{body_text}"
225
- )
226
- self.logger.debug(details)
227
- raise RuntimeError(details)
228
-
229
- async for line in response.aiter_lines():
230
- if not line:
231
- continue
232
- try:
233
- decoded = line
234
- except Exception:
235
- decoded = str(line)
236
- try:
237
- self._parse_output(
238
- execution,
239
- decoded,
240
- on_stdout=on_stdout,
241
- on_stderr=on_stderr,
242
- on_result=on_result,
243
- on_error=on_error,
201
+ async with self.process.get_client() as client:
202
+ timeout_cfg = httpx.Timeout(
203
+ connect=connect_timeout, read=read_timeout, write=write_timeout, pool=pool_timeout
204
+ )
205
+ async with client.stream(
206
+ "POST",
207
+ "/port/8888/execute",
208
+ json=body,
209
+ timeout=timeout_cfg,
210
+ ) as response:
211
+ if response.status_code >= 400:
212
+ try:
213
+ body_text = await response.aread()
214
+ body_text = body_text.decode(errors="ignore")
215
+ except Exception:
216
+ body_text = "<unavailable>"
217
+ req = getattr(response, "request", None)
218
+ method = getattr(req, "method", "UNKNOWN") if req else "UNKNOWN"
219
+ url = str(getattr(req, "url", "UNKNOWN")) if req else "UNKNOWN"
220
+ reason = getattr(response, "reason_phrase", "")
221
+ details = (
222
+ "Execution failed\n"
223
+ f"- method: {method}\n- url: {url}\n- status: {response.status_code} {reason}\n"
224
+ f"- response-headers: {dict(response.headers)}\n- body:\n{body_text}"
244
225
  )
245
- except json.JSONDecodeError:
226
+ self.logger.debug(details)
227
+ raise RuntimeError(details)
228
+
229
+ async for line in response.aiter_lines():
230
+ if not line:
231
+ continue
232
+ try:
233
+ decoded = line
234
+ except Exception:
235
+ decoded = str(line)
236
+ try:
237
+ self._parse_output(
238
+ execution,
239
+ decoded,
240
+ on_stdout=on_stdout,
241
+ on_stderr=on_stderr,
242
+ on_result=on_result,
243
+ on_error=on_error,
244
+ )
245
+ except json.JSONDecodeError:
246
246
  # Fallback: treat as stdout text-only message
247
- execution.logs.stdout.append(decoded)
248
- if on_stdout:
249
- on_stdout(CodeInterpreter.OutputMessage(decoded, None, False))
247
+ execution.logs.stdout.append(decoded)
248
+ if on_stdout:
249
+ on_stdout(CodeInterpreter.OutputMessage(decoded, None, False))
250
250
 
251
251
  return execution
252
252
 
@@ -262,19 +262,15 @@ class CodeInterpreter(SandboxInstance):
262
262
  if cwd:
263
263
  data["cwd"] = cwd
264
264
 
265
- client = self.process.get_client()
266
- response = await client.post(
267
- "/port/8888/contexts",
268
- json=data,
269
- timeout=request_timeout or 10.0,
270
- )
271
- try:
272
- # Always read response body first
273
- body_bytes = await response.aread()
274
-
265
+ async with self.process.get_client() as client:
266
+ response = await client.post(
267
+ "/port/8888/contexts",
268
+ json=data,
269
+ timeout=request_timeout or 10.0,
270
+ )
275
271
  if response.status_code >= 400:
276
272
  try:
277
- body_text = body_bytes.decode('utf-8', errors='ignore')
273
+ body_text = response.text
278
274
  except Exception:
279
275
  body_text = "<unavailable>"
280
276
  method = getattr(response.request, "method", "UNKNOWN")
@@ -287,10 +283,7 @@ class CodeInterpreter(SandboxInstance):
287
283
  )
288
284
  self.logger.debug(details)
289
285
  raise RuntimeError(details)
290
-
291
- data = json.loads(body_bytes)
286
+ data = response.json()
292
287
  return CodeInterpreter.Context.from_json(data)
293
- finally:
294
- await response.aclose()
295
288
 
296
289