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.
- blaxel/__init__.py +3 -5
- blaxel/core/__init__.py +9 -1
- blaxel/core/client/api/{privateclusters/create_private_cluster.py → images/cleanup_images.py} +31 -25
- blaxel/core/client/models/__init__.py +2 -4
- blaxel/core/client/models/agent.py +1 -0
- blaxel/core/client/models/agent_spec.py +1 -24
- blaxel/core/client/models/billable_time_metric.py +1 -0
- blaxel/core/{sandbox/client/models/find_match.py → client/models/cleanup_images_response_200.py} +19 -19
- blaxel/core/client/models/configuration.py +1 -0
- blaxel/core/client/models/core_event.py +9 -0
- blaxel/core/client/models/core_spec.py +1 -24
- blaxel/core/client/models/core_spec_configurations.py +1 -0
- blaxel/core/client/models/create_job_execution_request.py +1 -0
- blaxel/core/client/models/create_job_execution_response.py +1 -0
- blaxel/core/client/models/custom_domain.py +1 -0
- blaxel/core/client/models/custom_domain_metadata.py +1 -0
- blaxel/core/client/models/custom_domain_spec.py +1 -0
- blaxel/core/client/models/delete_volume_template_version_response_200.py +1 -0
- blaxel/core/client/models/entrypoint.py +1 -0
- blaxel/core/client/models/form.py +1 -0
- blaxel/core/client/models/function.py +1 -0
- blaxel/core/client/models/function_spec.py +1 -24
- blaxel/core/client/models/image.py +1 -0
- blaxel/core/client/models/image_spec.py +1 -0
- blaxel/core/client/models/integration.py +1 -0
- blaxel/core/client/models/integration_connection.py +1 -0
- blaxel/core/client/models/integration_connection_spec.py +1 -0
- blaxel/core/client/models/integration_endpoint.py +1 -0
- blaxel/core/client/models/integration_endpoints.py +2 -0
- blaxel/core/client/models/job.py +1 -0
- blaxel/core/client/models/job_execution.py +1 -0
- blaxel/core/client/models/job_execution_spec.py +1 -0
- blaxel/core/client/models/job_execution_task.py +1 -0
- blaxel/core/client/models/job_metrics.py +1 -0
- blaxel/core/client/models/job_spec.py +1 -24
- blaxel/core/client/models/jobs_network_chart.py +1 -0
- blaxel/core/client/models/jobs_success_failed_chart.py +1 -0
- blaxel/core/client/models/latency_metric.py +1 -0
- blaxel/core/client/models/location_response.py +1 -0
- blaxel/core/client/models/mcp_definition.py +1 -0
- blaxel/core/client/models/metadata.py +1 -0
- blaxel/core/client/models/metrics.py +34 -0
- blaxel/core/client/models/model.py +1 -0
- blaxel/core/client/models/model_spec.py +1 -24
- blaxel/core/client/models/pending_invitation_accept.py +1 -0
- blaxel/core/client/models/pending_invitation_render.py +1 -0
- blaxel/core/client/models/policy.py +1 -0
- blaxel/core/client/models/policy_spec.py +1 -0
- blaxel/core/client/models/preview.py +1 -0
- blaxel/core/client/models/preview_spec.py +1 -0
- blaxel/core/client/models/preview_token.py +1 -0
- blaxel/core/client/models/public_ips.py +1 -0
- blaxel/core/client/models/request_duration_over_time_metrics.py +1 -0
- blaxel/core/client/models/request_total_by_origin_metric.py +1 -0
- blaxel/core/client/models/request_total_metric.py +1 -0
- blaxel/core/client/models/resource_metrics.py +1 -0
- blaxel/core/client/models/revision_configuration.py +9 -0
- blaxel/core/client/models/runtime.py +1 -0
- blaxel/core/client/models/sandbox.py +1 -0
- blaxel/core/client/models/sandbox_definition.py +1 -0
- blaxel/core/client/models/sandbox_lifecycle.py +1 -0
- blaxel/core/client/models/sandbox_spec.py +1 -24
- blaxel/core/client/models/serverless_config.py +1 -0
- blaxel/core/client/models/start_sandbox.py +1 -0
- blaxel/core/client/models/stop_sandbox.py +1 -0
- blaxel/core/client/models/store_agent.py +1 -0
- blaxel/core/client/models/store_configuration.py +1 -0
- blaxel/core/client/models/template.py +1 -0
- blaxel/core/client/models/time_to_first_token_over_time_metrics.py +1 -0
- blaxel/core/client/models/token_rate_metrics.py +1 -0
- blaxel/core/client/models/trigger.py +10 -0
- blaxel/core/client/models/trigger_configuration.py +28 -0
- blaxel/core/client/models/volume.py +1 -0
- blaxel/core/client/models/volume_template.py +1 -0
- blaxel/core/client/models/websocket_channel.py +9 -0
- blaxel/core/client/models/workspace.py +1 -0
- blaxel/core/client/response_interceptor.py +0 -1
- blaxel/core/common/__init__.py +11 -2
- blaxel/core/common/autoload.py +1 -85
- blaxel/core/common/settings.py +56 -16
- blaxel/core/common/webhook.py +187 -0
- blaxel/core/jobs/__init__.py +352 -3
- blaxel/core/sandbox/client/models/__init__.py +0 -16
- blaxel/core/sandbox/default/action.py +10 -27
- blaxel/core/sandbox/default/filesystem.py +47 -185
- blaxel/core/sandbox/default/interpreter.py +55 -62
- blaxel/core/sandbox/default/process.py +46 -66
- {blaxel-0.2.26rc119.dist-info → blaxel-0.2.27.dist-info}/METADATA +2 -3
- {blaxel-0.2.26rc119.dist-info → blaxel-0.2.27.dist-info}/RECORD +91 -114
- blaxel/core/client/api/privateclusters/__init__.py +0 -0
- blaxel/core/client/api/privateclusters/delete_private_cluster.py +0 -152
- blaxel/core/client/api/privateclusters/get_private_cluster.py +0 -155
- blaxel/core/client/api/privateclusters/get_private_cluster_health.py +0 -97
- blaxel/core/client/api/privateclusters/list_private_clusters.py +0 -136
- blaxel/core/client/api/privateclusters/update_private_cluster.py +0 -152
- blaxel/core/client/api/privateclusters/update_private_cluster_health.py +0 -97
- blaxel/core/client/models/model_private_cluster.py +0 -79
- blaxel/core/client/models/private_cluster.py +0 -183
- blaxel/core/sandbox/client/api/filesystem/delete_filesystem_tree_path.py +0 -188
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_content_search_path.py +0 -265
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_find_path.py +0 -248
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_search_path.py +0 -237
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_tree_path.py +0 -197
- blaxel/core/sandbox/client/api/filesystem/put_filesystem_tree_path.py +0 -223
- blaxel/core/sandbox/client/api/websocket/__init__.py +0 -0
- blaxel/core/sandbox/client/api/websocket/get_ws.py +0 -81
- blaxel/core/sandbox/client/models/content_search_match.py +0 -98
- blaxel/core/sandbox/client/models/content_search_response.py +0 -97
- blaxel/core/sandbox/client/models/find_response.py +0 -88
- blaxel/core/sandbox/client/models/fuzzy_search_match.py +0 -78
- blaxel/core/sandbox/client/models/fuzzy_search_response.py +0 -88
- blaxel/core/sandbox/client/models/tree_request.py +0 -76
- blaxel/core/sandbox/client/models/tree_request_files.py +0 -49
- {blaxel-0.2.26rc119.dist-info → blaxel-0.2.27.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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(
|
|
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
|
-
|
|
58
|
-
|
|
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(
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
return SuccessResponse.from_dict(json
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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(
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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(
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
452
|
-
|
|
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
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
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
|
-
|
|
491
|
-
|
|
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(
|
|
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
|
-
|
|
505
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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 =
|
|
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
|
|