blaxel 0.2.29__py3-none-any.whl → 0.2.31__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 (185) hide show
  1. blaxel/__init__.py +3 -3
  2. blaxel/core/agents/__init__.py +13 -6
  3. blaxel/core/authentication/__init__.py +2 -1
  4. blaxel/core/authentication/devicemode.py +9 -1
  5. blaxel/core/authentication/oauth.py +13 -6
  6. blaxel/core/authentication/types.py +1 -0
  7. blaxel/core/cache/cache.py +10 -3
  8. blaxel/core/client/api/agents/list_agent_revisions.py +3 -1
  9. blaxel/core/client/api/compute/delete_sandbox_preview_token.py +6 -2
  10. blaxel/core/client/api/compute/start_sandbox.py +3 -1
  11. blaxel/core/client/api/compute/stop_sandbox.py +3 -1
  12. blaxel/core/client/api/default/list_sandbox_hub_definitions.py +6 -2
  13. blaxel/core/client/api/functions/list_function_revisions.py +3 -1
  14. blaxel/core/client/api/images/cleanup_images.py +3 -1
  15. blaxel/core/client/api/integrations/list_integration_connections.py +6 -2
  16. blaxel/core/client/api/invitations/list_all_pending_invitations.py +3 -1
  17. blaxel/core/client/api/jobs/create_job_execution.py +3 -1
  18. blaxel/core/client/api/jobs/delete_job_execution.py +3 -1
  19. blaxel/core/client/api/jobs/get_job_execution.py +3 -1
  20. blaxel/core/client/api/jobs/list_job_executions.py +6 -2
  21. blaxel/core/client/api/jobs/list_job_revisions.py +3 -1
  22. blaxel/core/client/api/locations/list_locations.py +3 -1
  23. blaxel/core/client/api/models/list_model_revisions.py +3 -1
  24. blaxel/core/client/api/service_accounts/create_workspace_service_account.py +6 -2
  25. blaxel/core/client/api/service_accounts/delete_workspace_service_account.py +6 -2
  26. blaxel/core/client/api/service_accounts/get_workspace_service_accounts.py +3 -1
  27. blaxel/core/client/api/service_accounts/update_workspace_service_account.py +6 -2
  28. blaxel/core/client/api/volume_templates/list_volume_templates.py +3 -1
  29. blaxel/core/client/api/workspaces/accept_workspace_invitation.py +6 -2
  30. blaxel/core/client/api/workspaces/invite_workspace_user.py +6 -2
  31. blaxel/core/client/api/workspaces/update_workspace_user_role.py +6 -2
  32. blaxel/core/client/client.py +3 -1
  33. blaxel/core/client/models/agent.py +11 -4
  34. blaxel/core/client/models/agent_spec.py +18 -5
  35. blaxel/core/client/models/billable_time_metric.py +0 -1
  36. blaxel/core/client/models/configuration.py +0 -1
  37. blaxel/core/client/models/core_spec.py +10 -3
  38. blaxel/core/client/models/core_spec_configurations.py +0 -1
  39. blaxel/core/client/models/create_job_execution_request.py +0 -1
  40. blaxel/core/client/models/create_job_execution_response.py +0 -1
  41. blaxel/core/client/models/custom_domain.py +5 -2
  42. blaxel/core/client/models/custom_domain_metadata.py +0 -1
  43. blaxel/core/client/models/custom_domain_spec.py +5 -2
  44. blaxel/core/client/models/delete_volume_template_version_response_200.py +5 -2
  45. blaxel/core/client/models/entrypoint.py +0 -1
  46. blaxel/core/client/models/form.py +5 -2
  47. blaxel/core/client/models/function.py +11 -4
  48. blaxel/core/client/models/function_spec.py +13 -4
  49. blaxel/core/client/models/image.py +5 -2
  50. blaxel/core/client/models/image_spec.py +0 -1
  51. blaxel/core/client/models/integration.py +10 -3
  52. blaxel/core/client/models/integration_connection.py +5 -2
  53. blaxel/core/client/models/integration_connection_spec.py +0 -1
  54. blaxel/core/client/models/integration_endpoint.py +5 -2
  55. blaxel/core/client/models/integration_endpoints.py +0 -2
  56. blaxel/core/client/models/job.py +11 -4
  57. blaxel/core/client/models/job_execution.py +5 -2
  58. blaxel/core/client/models/job_execution_spec.py +0 -1
  59. blaxel/core/client/models/job_execution_task.py +5 -2
  60. blaxel/core/client/models/job_metrics.py +5 -2
  61. blaxel/core/client/models/job_spec.py +13 -4
  62. blaxel/core/client/models/jobs_network_chart.py +0 -1
  63. blaxel/core/client/models/jobs_success_failed_chart.py +10 -3
  64. blaxel/core/client/models/latency_metric.py +5 -2
  65. blaxel/core/client/models/location_response.py +0 -1
  66. blaxel/core/client/models/mcp_definition.py +5 -2
  67. blaxel/core/client/models/metadata.py +0 -1
  68. blaxel/core/client/models/metrics.py +11 -4
  69. blaxel/core/client/models/model.py +11 -4
  70. blaxel/core/client/models/model_spec.py +10 -3
  71. blaxel/core/client/models/pending_invitation_accept.py +5 -2
  72. blaxel/core/client/models/pending_invitation_render.py +10 -3
  73. blaxel/core/client/models/policy.py +5 -2
  74. blaxel/core/client/models/policy_spec.py +11 -4
  75. blaxel/core/client/models/preview.py +5 -2
  76. blaxel/core/client/models/preview_spec.py +0 -1
  77. blaxel/core/client/models/preview_token.py +5 -2
  78. blaxel/core/client/models/public_ips.py +0 -1
  79. blaxel/core/client/models/request_duration_over_time_metrics.py +0 -1
  80. blaxel/core/client/models/request_total_by_origin_metric.py +16 -7
  81. blaxel/core/client/models/request_total_metric.py +8 -3
  82. blaxel/core/client/models/resource_metrics.py +58 -17
  83. blaxel/core/client/models/runtime.py +0 -1
  84. blaxel/core/client/models/sandbox.py +11 -4
  85. blaxel/core/client/models/sandbox_definition.py +0 -1
  86. blaxel/core/client/models/sandbox_lifecycle.py +0 -1
  87. blaxel/core/client/models/sandbox_spec.py +21 -6
  88. blaxel/core/client/models/serverless_config.py +0 -1
  89. blaxel/core/client/models/start_sandbox.py +5 -2
  90. blaxel/core/client/models/stop_sandbox.py +5 -2
  91. blaxel/core/client/models/store_agent.py +0 -1
  92. blaxel/core/client/models/store_configuration.py +0 -1
  93. blaxel/core/client/models/template.py +0 -1
  94. blaxel/core/client/models/time_to_first_token_over_time_metrics.py +3 -2
  95. blaxel/core/client/models/token_rate_metrics.py +0 -1
  96. blaxel/core/client/models/trigger.py +0 -1
  97. blaxel/core/client/models/trigger_configuration.py +0 -1
  98. blaxel/core/client/models/volume.py +11 -4
  99. blaxel/core/client/models/volume_template.py +5 -2
  100. blaxel/core/client/models/workspace.py +5 -2
  101. blaxel/core/client/response_interceptor.py +3 -1
  102. blaxel/core/common/autoload.py +35 -3
  103. blaxel/core/common/env.py +10 -8
  104. blaxel/core/common/settings.py +4 -2
  105. blaxel/core/common/webhook.py +0 -1
  106. blaxel/core/jobs/__init__.py +13 -3
  107. blaxel/core/mcp/client.py +8 -2
  108. blaxel/core/mcp/server.py +8 -2
  109. blaxel/core/models/__init__.py +6 -5
  110. blaxel/core/sandbox/__init__.py +1 -1
  111. blaxel/core/sandbox/client/api/codegen/get_codegen_reranking_path.py +6 -2
  112. blaxel/core/sandbox/client/api/fastapply/put_codegen_fastapply_path.py +6 -2
  113. blaxel/core/sandbox/client/api/filesystem/delete_filesystem_multipart_upload_id_abort.py +6 -2
  114. blaxel/core/sandbox/client/api/filesystem/delete_filesystem_path.py +6 -2
  115. blaxel/core/sandbox/client/api/filesystem/delete_filesystem_tree_path.py +192 -0
  116. blaxel/core/sandbox/client/api/filesystem/get_filesystem_content_search_path.py +252 -0
  117. blaxel/core/sandbox/client/api/filesystem/get_filesystem_find_path.py +252 -0
  118. blaxel/core/sandbox/client/api/filesystem/get_filesystem_search_path.py +241 -0
  119. blaxel/core/sandbox/client/api/filesystem/get_filesystem_tree_path.py +197 -0
  120. blaxel/core/sandbox/client/api/filesystem/get_watch_filesystem_path.py +6 -2
  121. blaxel/core/sandbox/client/api/filesystem/post_filesystem_multipart_upload_id_complete.py +6 -2
  122. blaxel/core/sandbox/client/api/filesystem/put_filesystem_path.py +6 -2
  123. blaxel/core/sandbox/client/api/filesystem/put_filesystem_tree_path.py +223 -0
  124. blaxel/core/sandbox/client/api/process/delete_process_identifier.py +6 -2
  125. blaxel/core/sandbox/client/api/process/delete_process_identifier_kill.py +6 -2
  126. blaxel/core/sandbox/client/api/process/get_process.py +3 -1
  127. blaxel/core/sandbox/client/api/process/get_process_identifier.py +6 -2
  128. blaxel/core/sandbox/client/api/process/get_process_identifier_logs.py +6 -2
  129. blaxel/core/sandbox/client/api/process/get_process_identifier_logs_stream.py +6 -2
  130. blaxel/core/sandbox/client/api/process/post_process.py +6 -2
  131. blaxel/core/sandbox/client/client.py +3 -1
  132. blaxel/core/sandbox/client/models/__init__.py +16 -0
  133. blaxel/core/sandbox/client/models/content_search_match.py +98 -0
  134. blaxel/core/sandbox/client/models/content_search_response.py +97 -0
  135. blaxel/core/sandbox/client/models/filesystem_multipart_upload_parts.py +3 -1
  136. blaxel/core/sandbox/client/models/find_match.py +69 -0
  137. blaxel/core/sandbox/client/models/find_response.py +88 -0
  138. blaxel/core/sandbox/client/models/fuzzy_search_match.py +78 -0
  139. blaxel/core/sandbox/client/models/fuzzy_search_response.py +88 -0
  140. blaxel/core/sandbox/client/models/tree_request.py +76 -0
  141. blaxel/core/sandbox/client/models/tree_request_files.py +49 -0
  142. blaxel/core/sandbox/default/__init__.py +0 -1
  143. blaxel/core/sandbox/default/action.py +13 -9
  144. blaxel/core/sandbox/default/codegen.py +2 -4
  145. blaxel/core/sandbox/default/filesystem.py +210 -64
  146. blaxel/core/sandbox/default/interpreter.py +75 -61
  147. blaxel/core/sandbox/default/preview.py +6 -2
  148. blaxel/core/sandbox/default/process.py +88 -50
  149. blaxel/core/sandbox/default/sandbox.py +7 -2
  150. blaxel/core/sandbox/sync/__init__.py +0 -2
  151. blaxel/core/sandbox/sync/action.py +2 -3
  152. blaxel/core/sandbox/sync/codegen.py +1 -5
  153. blaxel/core/sandbox/sync/filesystem.py +17 -6
  154. blaxel/core/sandbox/sync/interpreter.py +10 -6
  155. blaxel/core/sandbox/sync/network.py +0 -2
  156. blaxel/core/sandbox/sync/preview.py +21 -9
  157. blaxel/core/sandbox/sync/process.py +32 -8
  158. blaxel/core/sandbox/sync/sandbox.py +13 -6
  159. blaxel/core/sandbox/sync/session.py +6 -4
  160. blaxel/core/sandbox/types.py +2 -1
  161. blaxel/core/tools/__init__.py +30 -6
  162. blaxel/core/tools/common.py +1 -1
  163. blaxel/core/tools/types.py +2 -1
  164. blaxel/crewai/model.py +20 -5
  165. blaxel/googleadk/__init__.py +1 -1
  166. blaxel/googleadk/tools.py +3 -5
  167. blaxel/langgraph/custom/gemini.py +126 -133
  168. blaxel/langgraph/model.py +54 -50
  169. blaxel/langgraph/tools.py +9 -3
  170. blaxel/llamaindex/custom/cohere.py +25 -16
  171. blaxel/llamaindex/model.py +44 -57
  172. blaxel/llamaindex/tools.py +2 -3
  173. blaxel/pydantic/custom/gemini.py +3 -3
  174. blaxel/pydantic/tools.py +2 -4
  175. blaxel/telemetry/exporters.py +10 -3
  176. blaxel/telemetry/instrumentation/blaxel_langgraph.py +4 -2
  177. blaxel/telemetry/instrumentation/blaxel_langgraph_gemini.py +22 -5
  178. blaxel/telemetry/instrumentation/utils.py +3 -3
  179. blaxel/telemetry/log/log.py +2 -3
  180. blaxel/telemetry/log/logger.py +21 -15
  181. blaxel/telemetry/span.py +10 -6
  182. {blaxel-0.2.29.dist-info → blaxel-0.2.31.dist-info}/METADATA +2 -2
  183. {blaxel-0.2.29.dist-info → blaxel-0.2.31.dist-info}/RECORD +185 -171
  184. {blaxel-0.2.29.dist-info → blaxel-0.2.31.dist-info}/WHEEL +0 -0
  185. {blaxel-0.2.29.dist-info → blaxel-0.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -34,31 +34,41 @@ class SandboxFileSystem(SandboxAction):
34
34
  path = self.format_path(path)
35
35
  body = FileRequest(is_directory=True, permissions=permissions)
36
36
 
37
- async with self.get_client() as client_instance:
38
- response = await client_instance.put(f"/filesystem/{path}", json=body.to_dict())
37
+ client = self.get_client()
38
+ response = await client.put(f"/filesystem/{path}", json=body.to_dict())
39
+ try:
40
+ data = json.loads(await response.aread())
39
41
  self.handle_response_error(response)
40
- return SuccessResponse.from_dict(response.json())
42
+ return SuccessResponse.from_dict(data)
43
+ finally:
44
+ await response.aclose()
41
45
 
42
46
  async def write(self, path: str, content: str) -> SuccessResponse:
43
47
  path = self.format_path(path)
44
48
 
45
49
  # Calculate content size in bytes
46
- content_size = len(content.encode('utf-8'))
50
+ content_size = len(content.encode("utf-8"))
47
51
 
48
52
  # Use multipart upload for large files
49
53
  if content_size > MULTIPART_THRESHOLD:
50
- content_bytes = content.encode('utf-8')
54
+ content_bytes = content.encode("utf-8")
51
55
  return await self._upload_with_multipart(path, content_bytes, "0644")
52
56
 
53
57
  # Use regular upload for small files
54
58
  body = FileRequest(content=content)
55
59
 
56
- async with self.get_client() as client_instance:
57
- response = await client_instance.put(f"/filesystem/{path}", json=body.to_dict())
60
+ client = self.get_client()
61
+ response = await client.put(f"/filesystem/{path}", json=body.to_dict())
62
+ try:
63
+ data = json.loads(await response.aread())
58
64
  self.handle_response_error(response)
59
- return SuccessResponse.from_dict(response.json())
65
+ return SuccessResponse.from_dict(data)
66
+ finally:
67
+ await response.aclose()
60
68
 
61
- async def write_binary(self, path: str, content: Union[bytes, bytearray, str]) -> SuccessResponse:
69
+ async def write_binary(
70
+ self, path: str, content: Union[bytes, bytearray, str]
71
+ ) -> SuccessResponse:
62
72
  """Write binary content to a file.
63
73
 
64
74
  Args:
@@ -88,7 +98,11 @@ class SandboxFileSystem(SandboxAction):
88
98
 
89
99
  # Prepare multipart form data
90
100
  files = {
91
- "file": ("binary-file.bin", binary_file, "application/octet-stream"),
101
+ "file": (
102
+ "binary-file.bin",
103
+ binary_file,
104
+ "application/octet-stream",
105
+ ),
92
106
  }
93
107
  data = {"permissions": "0644", "path": path}
94
108
 
@@ -96,13 +110,16 @@ class SandboxFileSystem(SandboxAction):
96
110
  url = f"{self.url}/filesystem/{path}"
97
111
  headers = {**settings.headers, **self.sandbox_config.headers}
98
112
 
99
- async with self.get_client() as client_instance:
100
- response = await client_instance.put(url, files=files, data=data, headers=headers)
101
-
113
+ client = self.get_client()
114
+ response = await client.put(url, files=files, data=data, headers=headers)
115
+ try:
116
+ content_bytes = await response.aread()
102
117
  if not response.is_success:
103
- raise Exception(f"Failed to write binary: {response.status_code} {response.text}")
104
-
105
- return SuccessResponse.from_dict(response.json())
118
+ error_text = content_bytes.decode("utf-8", errors="ignore")
119
+ raise Exception(f"Failed to write binary: {response.status_code} {error_text}")
120
+ return SuccessResponse.from_dict(json.loads(content_bytes))
121
+ finally:
122
+ await response.aclose()
106
123
 
107
124
  async def write_tree(
108
125
  self,
@@ -118,26 +135,32 @@ class SandboxFileSystem(SandboxAction):
118
135
 
119
136
  path = destination_path or ""
120
137
 
121
- async with self.get_client() as client_instance:
122
- response = await client_instance.put(
123
- f"/filesystem/tree/{path}",
124
- json={"files": files_dict},
125
- headers={"Content-Type": "application/json"},
126
- )
138
+ client = self.get_client()
139
+ response = await client.put(
140
+ f"/filesystem/tree/{path}",
141
+ json={"files": files_dict},
142
+ headers={"Content-Type": "application/json"},
143
+ )
144
+ try:
145
+ data = json.loads(await response.aread())
127
146
  self.handle_response_error(response)
128
- return Directory.from_dict(response.json())
147
+ return Directory.from_dict(data)
148
+ finally:
149
+ await response.aclose()
129
150
 
130
151
  async def read(self, path: str) -> str:
131
152
  path = self.format_path(path)
132
153
 
133
- async with self.get_client() as client_instance:
134
- response = await client_instance.get(f"/filesystem/{path}")
154
+ client = self.get_client()
155
+ response = await client.get(f"/filesystem/{path}")
156
+ try:
157
+ data = json.loads(await response.aread())
135
158
  self.handle_response_error(response)
136
-
137
- data = response.json()
138
159
  if "content" in data:
139
160
  return data["content"]
140
161
  raise Exception("Unsupported file type")
162
+ finally:
163
+ await response.aclose()
141
164
 
142
165
  async def read_binary(self, path: str) -> bytes:
143
166
  """Read binary content from a file.
@@ -157,10 +180,14 @@ class SandboxFileSystem(SandboxAction):
157
180
  "Accept": "application/octet-stream",
158
181
  }
159
182
 
160
- async with self.get_client() as client_instance:
161
- response = await client_instance.get(url, headers=headers)
183
+ client = self.get_client()
184
+ response = await client.get(url, headers=headers)
185
+ try:
186
+ content = await response.aread()
162
187
  self.handle_response_error(response)
163
- return response.content
188
+ return content
189
+ finally:
190
+ await response.aclose()
164
191
 
165
192
  async def download(self, src: str, destination_path: str, mode: int = 0o644) -> None:
166
193
  """Download a file from the sandbox to the local filesystem.
@@ -178,23 +205,135 @@ class SandboxFileSystem(SandboxAction):
178
205
  async def rm(self, path: str, recursive: bool = False) -> SuccessResponse:
179
206
  path = self.format_path(path)
180
207
 
181
- async with self.get_client() as client_instance:
182
- params = {"recursive": "true"} if recursive else {}
183
- response = await client_instance.delete(f"/filesystem/{path}", params=params)
208
+ client = self.get_client()
209
+ params = {"recursive": "true"} if recursive else {}
210
+ response = await client.delete(f"/filesystem/{path}", params=params)
211
+ try:
212
+ data = json.loads(await response.aread())
184
213
  self.handle_response_error(response)
185
- return SuccessResponse.from_dict(response.json())
214
+ return SuccessResponse.from_dict(data)
215
+ finally:
216
+ await response.aclose()
186
217
 
187
218
  async def ls(self, path: str) -> Directory:
188
219
  path = self.format_path(path)
189
220
 
190
- async with self.get_client() as client_instance:
191
- response = await client_instance.get(f"/filesystem/{path}")
221
+ client = self.get_client()
222
+ response = await client.get(f"/filesystem/{path}")
223
+ try:
224
+ data = json.loads(await response.aread())
192
225
  self.handle_response_error(response)
193
-
194
- data = response.json()
195
226
  if not ("files" in data or "subdirectories" in data):
196
227
  raise Exception('{"error": "Directory not found"}')
197
228
  return Directory.from_dict(data)
229
+ finally:
230
+ await response.aclose()
231
+
232
+ async def find(
233
+ self,
234
+ path: str,
235
+ type: str | None = None,
236
+ patterns: List[str] | None = None,
237
+ max_results: int | None = None,
238
+ exclude_dirs: List[str] | None = None,
239
+ exclude_hidden: bool | None = None,
240
+ ):
241
+ """Find files and directories.
242
+
243
+ Args:
244
+ path: Path to search in
245
+ type: Type of search ('file' or 'directory')
246
+ patterns: File patterns to include (e.g., ['*.py', '*.json'])
247
+ max_results: Maximum number of results to return
248
+ exclude_dirs: Directory names to skip
249
+ exclude_hidden: Exclude hidden files and directories
250
+
251
+ Returns:
252
+ FindResponse with matching files/directories
253
+ """
254
+ path = self.format_path(path)
255
+
256
+ params = {}
257
+ if type is not None:
258
+ params["type"] = type
259
+ if patterns is not None and len(patterns) > 0:
260
+ params["patterns"] = ",".join(patterns)
261
+ if max_results is not None:
262
+ params["maxResults"] = max_results
263
+ if exclude_dirs is not None and len(exclude_dirs) > 0:
264
+ params["excludeDirs"] = ",".join(exclude_dirs)
265
+ if exclude_hidden is not None:
266
+ params["excludeHidden"] = exclude_hidden
267
+
268
+ url = f"{self.url}/filesystem-find/{path}"
269
+ headers = {**settings.headers, **self.sandbox_config.headers}
270
+
271
+ client = self.get_client()
272
+ response = await client.get(url, params=params, headers=headers)
273
+ try:
274
+ data = json.loads(await response.aread())
275
+ self.handle_response_error(response)
276
+
277
+ from ..client.models.find_response import FindResponse
278
+
279
+ return FindResponse.from_dict(data)
280
+ finally:
281
+ await response.aclose()
282
+
283
+ async def grep(
284
+ self,
285
+ query: str,
286
+ path: str = "/",
287
+ case_sensitive: bool | None = None,
288
+ context_lines: int | None = None,
289
+ max_results: int | None = None,
290
+ file_pattern: str | None = None,
291
+ exclude_dirs: List[str] | None = None,
292
+ ):
293
+ """Search for text content inside files using ripgrep.
294
+
295
+ Args:
296
+ query: Text to search for
297
+ path: Directory path to search in
298
+ case_sensitive: Case sensitive search (default: false)
299
+ context_lines: Number of context lines to include (default: 0)
300
+ max_results: Maximum number of results to return (default: 100)
301
+ file_pattern: File pattern to include (e.g., '*.py')
302
+ exclude_dirs: Directory names to skip
303
+
304
+ Returns:
305
+ ContentSearchResponse with matching lines
306
+ """
307
+ path = self.format_path(path)
308
+
309
+ params = {"query": query}
310
+ if case_sensitive is not None:
311
+ params["caseSensitive"] = case_sensitive
312
+ if context_lines is not None:
313
+ params["contextLines"] = context_lines
314
+ if max_results is not None:
315
+ params["maxResults"] = max_results
316
+ if file_pattern is not None:
317
+ params["filePattern"] = file_pattern
318
+ if exclude_dirs is not None and len(exclude_dirs) > 0:
319
+ params["excludeDirs"] = ",".join(exclude_dirs)
320
+
321
+ url = f"{self.url}/filesystem-content-search/{path}"
322
+ headers = {**settings.headers, **self.sandbox_config.headers}
323
+
324
+ client = self.get_client()
325
+ response = await client.get(url, params=params, headers=headers)
326
+ try:
327
+ data = json.loads(await response.aread())
328
+ self.handle_response_error(response)
329
+
330
+ from ..client.models.content_search_response import (
331
+ ContentSearchResponse,
332
+ )
333
+
334
+ return ContentSearchResponse.from_dict(data)
335
+ finally:
336
+ await response.aclose()
198
337
 
199
338
  async def cp(self, source: str, destination: str, max_wait: int = 180000) -> CopyResponse:
200
339
  """Copy files or directories using the cp command.
@@ -208,9 +347,7 @@ class SandboxFileSystem(SandboxAction):
208
347
  raise Exception("Process instance not available. Cannot execute cp command.")
209
348
 
210
349
  # Execute cp -r command
211
- process = await self.process.exec({
212
- "command": f"cp -r {source} {destination}"
213
- })
350
+ process = await self.process.exec({"command": f"cp -r {source} {destination}"})
214
351
 
215
352
  # Wait for process to complete
216
353
  process = await self.process.wait(process.pid, max_wait=max_wait, interval=100)
@@ -220,11 +357,7 @@ class SandboxFileSystem(SandboxAction):
220
357
  logs = process.logs if hasattr(process, "logs") else "Unknown error"
221
358
  raise Exception(f"Could not copy {source} to {destination} cause: {logs}")
222
359
 
223
- return CopyResponse(
224
- message="Files copied",
225
- source=source,
226
- destination=destination
227
- )
360
+ return CopyResponse(message="Files copied", source=source, destination=destination)
228
361
 
229
362
  def watch(
230
363
  self,
@@ -321,21 +454,25 @@ class SandboxFileSystem(SandboxAction):
321
454
  return path
322
455
 
323
456
  # Multipart upload helper methods
324
- async def _initiate_multipart_upload(self, path: str, permissions: str = "0644") -> Dict[str, Any]:
457
+ async def _initiate_multipart_upload(
458
+ self, path: str, permissions: str = "0644"
459
+ ) -> Dict[str, Any]:
325
460
  """Initiate a multipart upload session."""
326
461
  path = self.format_path(path)
327
462
  url = f"{self.url}/filesystem-multipart/initiate/{path}"
328
463
  headers = {**settings.headers, **self.sandbox_config.headers}
329
464
  body = {"permissions": permissions}
330
465
 
331
- async with self.get_client() as client_instance:
332
- response = await client_instance.post(url, json=body, headers=headers)
466
+ client = self.get_client()
467
+ response = await client.post(url, json=body, headers=headers)
468
+ try:
469
+ data = json.loads(await response.aread())
333
470
  self.handle_response_error(response)
334
- return response.json()
471
+ return data
472
+ finally:
473
+ await response.aclose()
335
474
 
336
- async def _upload_part(
337
- self, upload_id: str, part_number: int, data: bytes
338
- ) -> Dict[str, Any]:
475
+ async def _upload_part(self, upload_id: str, part_number: int, data: bytes) -> Dict[str, Any]:
339
476
  """Upload a single part of a multipart upload."""
340
477
  url = f"{self.url}/filesystem-multipart/{upload_id}/part"
341
478
  headers = {**settings.headers, **self.sandbox_config.headers}
@@ -344,12 +481,14 @@ class SandboxFileSystem(SandboxAction):
344
481
  # Prepare multipart form data with the file chunk
345
482
  files = {"file": ("part", io.BytesIO(data), "application/octet-stream")}
346
483
 
347
- async with self.get_client() as client_instance:
348
- response = await client_instance.put(
349
- url, files=files, params=params, headers=headers
350
- )
484
+ client = self.get_client()
485
+ response = await client.put(url, files=files, params=params, headers=headers)
486
+ try:
487
+ data = json.loads(await response.aread())
351
488
  self.handle_response_error(response)
352
- return response.json()
489
+ return data
490
+ finally:
491
+ await response.aclose()
353
492
 
354
493
  async def _complete_multipart_upload(
355
494
  self, upload_id: str, parts: List[Dict[str, Any]]
@@ -359,21 +498,28 @@ class SandboxFileSystem(SandboxAction):
359
498
  headers = {**settings.headers, **self.sandbox_config.headers}
360
499
  body = {"parts": parts}
361
500
 
362
- async with self.get_client() as client_instance:
363
- response = await client_instance.post(url, json=body, headers=headers)
501
+ client = self.get_client()
502
+ response = await client.post(url, json=body, headers=headers)
503
+ try:
504
+ data = json.loads(await response.aread())
364
505
  self.handle_response_error(response)
365
- return SuccessResponse.from_dict(response.json())
506
+ return SuccessResponse.from_dict(data)
507
+ finally:
508
+ await response.aclose()
366
509
 
367
510
  async def _abort_multipart_upload(self, upload_id: str) -> None:
368
511
  """Abort a multipart upload and clean up all parts."""
369
512
  url = f"{self.url}/filesystem-multipart/{upload_id}/abort"
370
513
  headers = {**settings.headers, **self.sandbox_config.headers}
371
514
 
372
- async with self.get_client() as client_instance:
373
- response = await client_instance.delete(url, headers=headers)
515
+ client = self.get_client()
516
+ response = await client.delete(url, headers=headers)
517
+ try:
374
518
  # Don't raise error if abort fails - we want to throw the original error
375
519
  if not response.is_success:
376
- logger.warning(f"Warning: Failed to abort multipart upload: {response.status_code}")
520
+ print(f"Warning: Failed to abort multipart upload: {response.status_code}")
521
+ finally:
522
+ await response.aclose()
377
523
 
378
524
  async def _upload_with_multipart(
379
525
  self, path: str, data: bytes, permissions: str = "0644"
@@ -17,7 +17,9 @@ class CodeInterpreter(SandboxInstance):
17
17
  DEFAULT_PORTS = [
18
18
  {"name": "jupyter", "target": 8888, "protocol": "HTTP"},
19
19
  ]
20
- DEFAULT_LIFECYCLE = {"expirationPolicies": [{"type": "ttl-idle", "value": "30m", "action": "delete"}]}
20
+ DEFAULT_LIFECYCLE = {
21
+ "expirationPolicies": [{"type": "ttl-idle", "value": "30m", "action": "delete"}]
22
+ }
21
23
 
22
24
  @classmethod
23
25
  async def get(cls, sandbox_name: str) -> CodeInterpreter:
@@ -148,11 +150,15 @@ class CodeInterpreter(SandboxInstance):
148
150
  elif data_type == "stdout":
149
151
  execution.logs.stdout.append(data["text"])
150
152
  if on_stdout:
151
- return on_stdout(CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), False))
153
+ return on_stdout(
154
+ CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), False)
155
+ )
152
156
  elif data_type == "stderr":
153
157
  execution.logs.stderr.append(data["text"])
154
158
  if on_stderr:
155
- return on_stderr(CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), True))
159
+ return on_stderr(
160
+ CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), True)
161
+ )
156
162
  elif data_type == "error":
157
163
  execution.error = CodeInterpreter.ExecutionError(
158
164
  data.get("name", ""), data.get("value"), data.get("traceback")
@@ -198,55 +204,58 @@ class CodeInterpreter(SandboxInstance):
198
204
 
199
205
  execution = CodeInterpreter.Execution()
200
206
 
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}"
207
+ client = self.process.get_client()
208
+ timeout_cfg = httpx.Timeout(
209
+ connect=connect_timeout,
210
+ read=read_timeout,
211
+ write=write_timeout,
212
+ pool=pool_timeout,
213
+ )
214
+ async with client.stream(
215
+ "POST",
216
+ "/port/8888/execute",
217
+ json=body,
218
+ timeout=timeout_cfg,
219
+ ) as response:
220
+ if response.status_code >= 400:
221
+ try:
222
+ body_text = await response.aread()
223
+ body_text = body_text.decode(errors="ignore")
224
+ except Exception:
225
+ body_text = "<unavailable>"
226
+ req = getattr(response, "request", None)
227
+ method = getattr(req, "method", "UNKNOWN") if req else "UNKNOWN"
228
+ url = str(getattr(req, "url", "UNKNOWN")) if req else "UNKNOWN"
229
+ reason = getattr(response, "reason_phrase", "")
230
+ details = (
231
+ "Execution failed\n"
232
+ f"- method: {method}\n- url: {url}\n- status: {response.status_code} {reason}\n"
233
+ f"- response-headers: {dict(response.headers)}\n- body:\n{body_text}"
234
+ )
235
+ self.logger.debug(details)
236
+ raise RuntimeError(details)
237
+
238
+ async for line in response.aiter_lines():
239
+ if not line:
240
+ continue
241
+ try:
242
+ decoded = line
243
+ except Exception:
244
+ decoded = str(line)
245
+ try:
246
+ self._parse_output(
247
+ execution,
248
+ decoded,
249
+ on_stdout=on_stdout,
250
+ on_stderr=on_stderr,
251
+ on_result=on_result,
252
+ on_error=on_error,
225
253
  )
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
- # 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))
254
+ except json.JSONDecodeError:
255
+ # Fallback: treat as stdout text-only message
256
+ execution.logs.stdout.append(decoded)
257
+ if on_stdout:
258
+ on_stdout(CodeInterpreter.OutputMessage(decoded, None, False))
250
259
 
251
260
  return execution
252
261
 
@@ -262,15 +271,19 @@ class CodeInterpreter(SandboxInstance):
262
271
  if cwd:
263
272
  data["cwd"] = cwd
264
273
 
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
- )
274
+ client = self.process.get_client()
275
+ response = await client.post(
276
+ "/port/8888/contexts",
277
+ json=data,
278
+ timeout=request_timeout or 10.0,
279
+ )
280
+ try:
281
+ # Always read response body first
282
+ body_bytes = await response.aread()
283
+
271
284
  if response.status_code >= 400:
272
285
  try:
273
- body_text = response.text
286
+ body_text = body_bytes.decode("utf-8", errors="ignore")
274
287
  except Exception:
275
288
  body_text = "<unavailable>"
276
289
  method = getattr(response.request, "method", "UNKNOWN")
@@ -283,7 +296,8 @@ class CodeInterpreter(SandboxInstance):
283
296
  )
284
297
  self.logger.debug(details)
285
298
  raise RuntimeError(details)
286
- data = response.json()
287
- return CodeInterpreter.Context.from_json(data)
288
-
289
299
 
300
+ data = json.loads(body_bytes)
301
+ return CodeInterpreter.Context.from_json(data)
302
+ finally:
303
+ await response.aclose()
@@ -15,11 +15,15 @@ from ...client.api.compute.delete_sandbox_preview import (
15
15
  from ...client.api.compute.delete_sandbox_preview_token import (
16
16
  asyncio as delete_sandbox_preview_token,
17
17
  )
18
- from ...client.api.compute.get_sandbox_preview import asyncio as get_sandbox_preview
18
+ from ...client.api.compute.get_sandbox_preview import (
19
+ asyncio as get_sandbox_preview,
20
+ )
19
21
  from ...client.api.compute.list_sandbox_preview_tokens import (
20
22
  asyncio as list_sandbox_preview_tokens,
21
23
  )
22
- from ...client.api.compute.list_sandbox_previews import asyncio as list_sandbox_previews
24
+ from ...client.api.compute.list_sandbox_previews import (
25
+ asyncio as list_sandbox_previews,
26
+ )
23
27
  from ...client.client import client
24
28
  from ...client.models import (
25
29
  Preview,