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.
- blaxel/__init__.py +3 -3
- blaxel/core/agents/__init__.py +13 -6
- blaxel/core/authentication/__init__.py +2 -1
- blaxel/core/authentication/devicemode.py +9 -1
- blaxel/core/authentication/oauth.py +13 -6
- blaxel/core/authentication/types.py +1 -0
- blaxel/core/cache/cache.py +10 -3
- blaxel/core/client/api/agents/list_agent_revisions.py +3 -1
- blaxel/core/client/api/compute/delete_sandbox_preview_token.py +6 -2
- blaxel/core/client/api/compute/start_sandbox.py +3 -1
- blaxel/core/client/api/compute/stop_sandbox.py +3 -1
- blaxel/core/client/api/default/list_sandbox_hub_definitions.py +6 -2
- blaxel/core/client/api/functions/list_function_revisions.py +3 -1
- blaxel/core/client/api/images/cleanup_images.py +3 -1
- blaxel/core/client/api/integrations/list_integration_connections.py +6 -2
- blaxel/core/client/api/invitations/list_all_pending_invitations.py +3 -1
- blaxel/core/client/api/jobs/create_job_execution.py +3 -1
- blaxel/core/client/api/jobs/delete_job_execution.py +3 -1
- blaxel/core/client/api/jobs/get_job_execution.py +3 -1
- blaxel/core/client/api/jobs/list_job_executions.py +6 -2
- blaxel/core/client/api/jobs/list_job_revisions.py +3 -1
- blaxel/core/client/api/locations/list_locations.py +3 -1
- blaxel/core/client/api/models/list_model_revisions.py +3 -1
- blaxel/core/client/api/service_accounts/create_workspace_service_account.py +6 -2
- blaxel/core/client/api/service_accounts/delete_workspace_service_account.py +6 -2
- blaxel/core/client/api/service_accounts/get_workspace_service_accounts.py +3 -1
- blaxel/core/client/api/service_accounts/update_workspace_service_account.py +6 -2
- blaxel/core/client/api/volume_templates/list_volume_templates.py +3 -1
- blaxel/core/client/api/workspaces/accept_workspace_invitation.py +6 -2
- blaxel/core/client/api/workspaces/invite_workspace_user.py +6 -2
- blaxel/core/client/api/workspaces/update_workspace_user_role.py +6 -2
- blaxel/core/client/client.py +3 -1
- blaxel/core/client/models/agent.py +11 -4
- blaxel/core/client/models/agent_spec.py +18 -5
- blaxel/core/client/models/billable_time_metric.py +0 -1
- blaxel/core/client/models/configuration.py +0 -1
- blaxel/core/client/models/core_spec.py +10 -3
- blaxel/core/client/models/core_spec_configurations.py +0 -1
- blaxel/core/client/models/create_job_execution_request.py +0 -1
- blaxel/core/client/models/create_job_execution_response.py +0 -1
- blaxel/core/client/models/custom_domain.py +5 -2
- blaxel/core/client/models/custom_domain_metadata.py +0 -1
- blaxel/core/client/models/custom_domain_spec.py +5 -2
- blaxel/core/client/models/delete_volume_template_version_response_200.py +5 -2
- blaxel/core/client/models/entrypoint.py +0 -1
- blaxel/core/client/models/form.py +5 -2
- blaxel/core/client/models/function.py +11 -4
- blaxel/core/client/models/function_spec.py +13 -4
- blaxel/core/client/models/image.py +5 -2
- blaxel/core/client/models/image_spec.py +0 -1
- blaxel/core/client/models/integration.py +10 -3
- blaxel/core/client/models/integration_connection.py +5 -2
- blaxel/core/client/models/integration_connection_spec.py +0 -1
- blaxel/core/client/models/integration_endpoint.py +5 -2
- blaxel/core/client/models/integration_endpoints.py +0 -2
- blaxel/core/client/models/job.py +11 -4
- blaxel/core/client/models/job_execution.py +5 -2
- blaxel/core/client/models/job_execution_spec.py +0 -1
- blaxel/core/client/models/job_execution_task.py +5 -2
- blaxel/core/client/models/job_metrics.py +5 -2
- blaxel/core/client/models/job_spec.py +13 -4
- blaxel/core/client/models/jobs_network_chart.py +0 -1
- blaxel/core/client/models/jobs_success_failed_chart.py +10 -3
- blaxel/core/client/models/latency_metric.py +5 -2
- blaxel/core/client/models/location_response.py +0 -1
- blaxel/core/client/models/mcp_definition.py +5 -2
- blaxel/core/client/models/metadata.py +0 -1
- blaxel/core/client/models/metrics.py +11 -4
- blaxel/core/client/models/model.py +11 -4
- blaxel/core/client/models/model_spec.py +10 -3
- blaxel/core/client/models/pending_invitation_accept.py +5 -2
- blaxel/core/client/models/pending_invitation_render.py +10 -3
- blaxel/core/client/models/policy.py +5 -2
- blaxel/core/client/models/policy_spec.py +11 -4
- blaxel/core/client/models/preview.py +5 -2
- blaxel/core/client/models/preview_spec.py +0 -1
- blaxel/core/client/models/preview_token.py +5 -2
- blaxel/core/client/models/public_ips.py +0 -1
- blaxel/core/client/models/request_duration_over_time_metrics.py +0 -1
- blaxel/core/client/models/request_total_by_origin_metric.py +16 -7
- blaxel/core/client/models/request_total_metric.py +8 -3
- blaxel/core/client/models/resource_metrics.py +58 -17
- blaxel/core/client/models/runtime.py +0 -1
- blaxel/core/client/models/sandbox.py +11 -4
- blaxel/core/client/models/sandbox_definition.py +0 -1
- blaxel/core/client/models/sandbox_lifecycle.py +0 -1
- blaxel/core/client/models/sandbox_spec.py +21 -6
- blaxel/core/client/models/serverless_config.py +0 -1
- blaxel/core/client/models/start_sandbox.py +5 -2
- blaxel/core/client/models/stop_sandbox.py +5 -2
- blaxel/core/client/models/store_agent.py +0 -1
- blaxel/core/client/models/store_configuration.py +0 -1
- blaxel/core/client/models/template.py +0 -1
- blaxel/core/client/models/time_to_first_token_over_time_metrics.py +3 -2
- blaxel/core/client/models/token_rate_metrics.py +0 -1
- blaxel/core/client/models/trigger.py +0 -1
- blaxel/core/client/models/trigger_configuration.py +0 -1
- blaxel/core/client/models/volume.py +11 -4
- blaxel/core/client/models/volume_template.py +5 -2
- blaxel/core/client/models/workspace.py +5 -2
- blaxel/core/client/response_interceptor.py +3 -1
- blaxel/core/common/autoload.py +35 -3
- blaxel/core/common/env.py +10 -8
- blaxel/core/common/settings.py +4 -2
- blaxel/core/common/webhook.py +0 -1
- blaxel/core/jobs/__init__.py +13 -3
- blaxel/core/mcp/client.py +8 -2
- blaxel/core/mcp/server.py +8 -2
- blaxel/core/models/__init__.py +6 -5
- blaxel/core/sandbox/__init__.py +1 -1
- blaxel/core/sandbox/client/api/codegen/get_codegen_reranking_path.py +6 -2
- blaxel/core/sandbox/client/api/fastapply/put_codegen_fastapply_path.py +6 -2
- blaxel/core/sandbox/client/api/filesystem/delete_filesystem_multipart_upload_id_abort.py +6 -2
- blaxel/core/sandbox/client/api/filesystem/delete_filesystem_path.py +6 -2
- blaxel/core/sandbox/client/api/filesystem/delete_filesystem_tree_path.py +192 -0
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_content_search_path.py +252 -0
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_find_path.py +252 -0
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_search_path.py +241 -0
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_tree_path.py +197 -0
- blaxel/core/sandbox/client/api/filesystem/get_watch_filesystem_path.py +6 -2
- blaxel/core/sandbox/client/api/filesystem/post_filesystem_multipart_upload_id_complete.py +6 -2
- blaxel/core/sandbox/client/api/filesystem/put_filesystem_path.py +6 -2
- blaxel/core/sandbox/client/api/filesystem/put_filesystem_tree_path.py +223 -0
- blaxel/core/sandbox/client/api/process/delete_process_identifier.py +6 -2
- blaxel/core/sandbox/client/api/process/delete_process_identifier_kill.py +6 -2
- blaxel/core/sandbox/client/api/process/get_process.py +3 -1
- blaxel/core/sandbox/client/api/process/get_process_identifier.py +6 -2
- blaxel/core/sandbox/client/api/process/get_process_identifier_logs.py +6 -2
- blaxel/core/sandbox/client/api/process/get_process_identifier_logs_stream.py +6 -2
- blaxel/core/sandbox/client/api/process/post_process.py +6 -2
- blaxel/core/sandbox/client/client.py +3 -1
- blaxel/core/sandbox/client/models/__init__.py +16 -0
- blaxel/core/sandbox/client/models/content_search_match.py +98 -0
- blaxel/core/sandbox/client/models/content_search_response.py +97 -0
- blaxel/core/sandbox/client/models/filesystem_multipart_upload_parts.py +3 -1
- blaxel/core/sandbox/client/models/find_match.py +69 -0
- blaxel/core/sandbox/client/models/find_response.py +88 -0
- blaxel/core/sandbox/client/models/fuzzy_search_match.py +78 -0
- blaxel/core/sandbox/client/models/fuzzy_search_response.py +88 -0
- blaxel/core/sandbox/client/models/tree_request.py +76 -0
- blaxel/core/sandbox/client/models/tree_request_files.py +49 -0
- blaxel/core/sandbox/default/__init__.py +0 -1
- blaxel/core/sandbox/default/action.py +13 -9
- blaxel/core/sandbox/default/codegen.py +2 -4
- blaxel/core/sandbox/default/filesystem.py +210 -64
- blaxel/core/sandbox/default/interpreter.py +75 -61
- blaxel/core/sandbox/default/preview.py +6 -2
- blaxel/core/sandbox/default/process.py +88 -50
- blaxel/core/sandbox/default/sandbox.py +7 -2
- blaxel/core/sandbox/sync/__init__.py +0 -2
- blaxel/core/sandbox/sync/action.py +2 -3
- blaxel/core/sandbox/sync/codegen.py +1 -5
- blaxel/core/sandbox/sync/filesystem.py +17 -6
- blaxel/core/sandbox/sync/interpreter.py +10 -6
- blaxel/core/sandbox/sync/network.py +0 -2
- blaxel/core/sandbox/sync/preview.py +21 -9
- blaxel/core/sandbox/sync/process.py +32 -8
- blaxel/core/sandbox/sync/sandbox.py +13 -6
- blaxel/core/sandbox/sync/session.py +6 -4
- blaxel/core/sandbox/types.py +2 -1
- blaxel/core/tools/__init__.py +30 -6
- blaxel/core/tools/common.py +1 -1
- blaxel/core/tools/types.py +2 -1
- blaxel/crewai/model.py +20 -5
- blaxel/googleadk/__init__.py +1 -1
- blaxel/googleadk/tools.py +3 -5
- blaxel/langgraph/custom/gemini.py +126 -133
- blaxel/langgraph/model.py +54 -50
- blaxel/langgraph/tools.py +9 -3
- blaxel/llamaindex/custom/cohere.py +25 -16
- blaxel/llamaindex/model.py +44 -57
- blaxel/llamaindex/tools.py +2 -3
- blaxel/pydantic/custom/gemini.py +3 -3
- blaxel/pydantic/tools.py +2 -4
- blaxel/telemetry/exporters.py +10 -3
- blaxel/telemetry/instrumentation/blaxel_langgraph.py +4 -2
- blaxel/telemetry/instrumentation/blaxel_langgraph_gemini.py +22 -5
- blaxel/telemetry/instrumentation/utils.py +3 -3
- blaxel/telemetry/log/log.py +2 -3
- blaxel/telemetry/log/logger.py +21 -15
- blaxel/telemetry/span.py +10 -6
- {blaxel-0.2.29.dist-info → blaxel-0.2.31.dist-info}/METADATA +2 -2
- {blaxel-0.2.29.dist-info → blaxel-0.2.31.dist-info}/RECORD +185 -171
- {blaxel-0.2.29.dist-info → blaxel-0.2.31.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
38
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
57
|
-
|
|
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(
|
|
65
|
+
return SuccessResponse.from_dict(data)
|
|
66
|
+
finally:
|
|
67
|
+
await response.aclose()
|
|
60
68
|
|
|
61
|
-
async def write_binary(
|
|
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": (
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
return SuccessResponse.from_dict(
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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(
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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(
|
|
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
|
-
|
|
191
|
-
|
|
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(
|
|
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
|
-
|
|
332
|
-
|
|
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
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
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
|
-
|
|
363
|
-
|
|
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(
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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 = {
|
|
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(
|
|
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(
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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,
|