ucloud-sandbox 0.1.0b3__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 (151) hide show
  1. e2b_connect/__init__.py +1 -0
  2. e2b_connect/client.py +493 -0
  3. ucloud_sandbox/__init__.py +164 -0
  4. ucloud_sandbox/api/__init__.py +164 -0
  5. ucloud_sandbox/api/client/__init__.py +8 -0
  6. ucloud_sandbox/api/client/api/__init__.py +1 -0
  7. ucloud_sandbox/api/client/api/sandboxes/__init__.py +1 -0
  8. ucloud_sandbox/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
  9. ucloud_sandbox/api/client/api/sandboxes/get_sandboxes.py +176 -0
  10. ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
  11. ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
  12. ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
  13. ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
  14. ucloud_sandbox/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
  15. ucloud_sandbox/api/client/api/sandboxes/post_sandboxes.py +172 -0
  16. ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  17. ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
  18. ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
  19. ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
  20. ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
  21. ucloud_sandbox/api/client/api/templates/__init__.py +1 -0
  22. ucloud_sandbox/api/client/api/templates/delete_templates_template_id.py +157 -0
  23. ucloud_sandbox/api/client/api/templates/get_templates.py +172 -0
  24. ucloud_sandbox/api/client/api/templates/get_templates_template_id.py +195 -0
  25. ucloud_sandbox/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +217 -0
  26. ucloud_sandbox/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
  27. ucloud_sandbox/api/client/api/templates/patch_templates_template_id.py +183 -0
  28. ucloud_sandbox/api/client/api/templates/post_templates.py +172 -0
  29. ucloud_sandbox/api/client/api/templates/post_templates_template_id.py +181 -0
  30. ucloud_sandbox/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
  31. ucloud_sandbox/api/client/api/templates/post_v2_templates.py +172 -0
  32. ucloud_sandbox/api/client/api/templates/post_v3_templates.py +172 -0
  33. ucloud_sandbox/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
  34. ucloud_sandbox/api/client/client.py +286 -0
  35. ucloud_sandbox/api/client/errors.py +16 -0
  36. ucloud_sandbox/api/client/models/__init__.py +123 -0
  37. ucloud_sandbox/api/client/models/aws_registry.py +85 -0
  38. ucloud_sandbox/api/client/models/aws_registry_type.py +8 -0
  39. ucloud_sandbox/api/client/models/build_log_entry.py +89 -0
  40. ucloud_sandbox/api/client/models/build_status_reason.py +95 -0
  41. ucloud_sandbox/api/client/models/connect_sandbox.py +59 -0
  42. ucloud_sandbox/api/client/models/created_access_token.py +100 -0
  43. ucloud_sandbox/api/client/models/created_team_api_key.py +166 -0
  44. ucloud_sandbox/api/client/models/disk_metrics.py +91 -0
  45. ucloud_sandbox/api/client/models/error.py +67 -0
  46. ucloud_sandbox/api/client/models/gcp_registry.py +69 -0
  47. ucloud_sandbox/api/client/models/gcp_registry_type.py +8 -0
  48. ucloud_sandbox/api/client/models/general_registry.py +77 -0
  49. ucloud_sandbox/api/client/models/general_registry_type.py +8 -0
  50. ucloud_sandbox/api/client/models/identifier_masking_details.py +83 -0
  51. ucloud_sandbox/api/client/models/listed_sandbox.py +154 -0
  52. ucloud_sandbox/api/client/models/log_level.py +11 -0
  53. ucloud_sandbox/api/client/models/max_team_metric.py +78 -0
  54. ucloud_sandbox/api/client/models/mcp_type_0.py +44 -0
  55. ucloud_sandbox/api/client/models/new_access_token.py +59 -0
  56. ucloud_sandbox/api/client/models/new_sandbox.py +172 -0
  57. ucloud_sandbox/api/client/models/new_team_api_key.py +59 -0
  58. ucloud_sandbox/api/client/models/node.py +155 -0
  59. ucloud_sandbox/api/client/models/node_detail.py +165 -0
  60. ucloud_sandbox/api/client/models/node_metrics.py +122 -0
  61. ucloud_sandbox/api/client/models/node_status.py +11 -0
  62. ucloud_sandbox/api/client/models/node_status_change.py +79 -0
  63. ucloud_sandbox/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
  64. ucloud_sandbox/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
  65. ucloud_sandbox/api/client/models/resumed_sandbox.py +68 -0
  66. ucloud_sandbox/api/client/models/sandbox.py +145 -0
  67. ucloud_sandbox/api/client/models/sandbox_detail.py +183 -0
  68. ucloud_sandbox/api/client/models/sandbox_log.py +70 -0
  69. ucloud_sandbox/api/client/models/sandbox_log_entry.py +93 -0
  70. ucloud_sandbox/api/client/models/sandbox_log_entry_fields.py +44 -0
  71. ucloud_sandbox/api/client/models/sandbox_logs.py +91 -0
  72. ucloud_sandbox/api/client/models/sandbox_metric.py +118 -0
  73. ucloud_sandbox/api/client/models/sandbox_network_config.py +92 -0
  74. ucloud_sandbox/api/client/models/sandbox_state.py +9 -0
  75. ucloud_sandbox/api/client/models/sandboxes_with_metrics.py +59 -0
  76. ucloud_sandbox/api/client/models/team.py +83 -0
  77. ucloud_sandbox/api/client/models/team_api_key.py +158 -0
  78. ucloud_sandbox/api/client/models/team_metric.py +86 -0
  79. ucloud_sandbox/api/client/models/team_user.py +68 -0
  80. ucloud_sandbox/api/client/models/template.py +217 -0
  81. ucloud_sandbox/api/client/models/template_build.py +139 -0
  82. ucloud_sandbox/api/client/models/template_build_file_upload.py +70 -0
  83. ucloud_sandbox/api/client/models/template_build_info.py +126 -0
  84. ucloud_sandbox/api/client/models/template_build_request.py +115 -0
  85. ucloud_sandbox/api/client/models/template_build_request_v2.py +88 -0
  86. ucloud_sandbox/api/client/models/template_build_request_v3.py +88 -0
  87. ucloud_sandbox/api/client/models/template_build_start_v2.py +184 -0
  88. ucloud_sandbox/api/client/models/template_build_status.py +11 -0
  89. ucloud_sandbox/api/client/models/template_legacy.py +207 -0
  90. ucloud_sandbox/api/client/models/template_request_response_v3.py +83 -0
  91. ucloud_sandbox/api/client/models/template_step.py +91 -0
  92. ucloud_sandbox/api/client/models/template_update_request.py +59 -0
  93. ucloud_sandbox/api/client/models/template_with_builds.py +148 -0
  94. ucloud_sandbox/api/client/models/update_team_api_key.py +59 -0
  95. ucloud_sandbox/api/client/py.typed +1 -0
  96. ucloud_sandbox/api/client/types.py +54 -0
  97. ucloud_sandbox/api/client_async/__init__.py +50 -0
  98. ucloud_sandbox/api/client_sync/__init__.py +52 -0
  99. ucloud_sandbox/api/metadata.py +17 -0
  100. ucloud_sandbox/connection_config.py +217 -0
  101. ucloud_sandbox/envd/api.py +59 -0
  102. ucloud_sandbox/envd/filesystem/filesystem_connect.py +193 -0
  103. ucloud_sandbox/envd/filesystem/filesystem_pb2.py +76 -0
  104. ucloud_sandbox/envd/filesystem/filesystem_pb2.pyi +233 -0
  105. ucloud_sandbox/envd/process/process_connect.py +155 -0
  106. ucloud_sandbox/envd/process/process_pb2.py +92 -0
  107. ucloud_sandbox/envd/process/process_pb2.pyi +304 -0
  108. ucloud_sandbox/envd/rpc.py +61 -0
  109. ucloud_sandbox/envd/versions.py +6 -0
  110. ucloud_sandbox/exceptions.py +95 -0
  111. ucloud_sandbox/sandbox/commands/command_handle.py +69 -0
  112. ucloud_sandbox/sandbox/commands/main.py +39 -0
  113. ucloud_sandbox/sandbox/filesystem/filesystem.py +94 -0
  114. ucloud_sandbox/sandbox/filesystem/watch_handle.py +60 -0
  115. ucloud_sandbox/sandbox/main.py +194 -0
  116. ucloud_sandbox/sandbox/network.py +8 -0
  117. ucloud_sandbox/sandbox/sandbox_api.py +181 -0
  118. ucloud_sandbox/sandbox/signature.py +45 -0
  119. ucloud_sandbox/sandbox/utils.py +34 -0
  120. ucloud_sandbox/sandbox_async/commands/command.py +336 -0
  121. ucloud_sandbox/sandbox_async/commands/command_handle.py +196 -0
  122. ucloud_sandbox/sandbox_async/commands/pty.py +193 -0
  123. ucloud_sandbox/sandbox_async/filesystem/filesystem.py +531 -0
  124. ucloud_sandbox/sandbox_async/filesystem/watch_handle.py +62 -0
  125. ucloud_sandbox/sandbox_async/main.py +686 -0
  126. ucloud_sandbox/sandbox_async/paginator.py +69 -0
  127. ucloud_sandbox/sandbox_async/sandbox_api.py +322 -0
  128. ucloud_sandbox/sandbox_async/utils.py +7 -0
  129. ucloud_sandbox/sandbox_sync/commands/command.py +328 -0
  130. ucloud_sandbox/sandbox_sync/commands/command_handle.py +150 -0
  131. ucloud_sandbox/sandbox_sync/commands/pty.py +186 -0
  132. ucloud_sandbox/sandbox_sync/filesystem/filesystem.py +518 -0
  133. ucloud_sandbox/sandbox_sync/filesystem/watch_handle.py +69 -0
  134. ucloud_sandbox/sandbox_sync/main.py +680 -0
  135. ucloud_sandbox/sandbox_sync/paginator.py +69 -0
  136. ucloud_sandbox/sandbox_sync/sandbox_api.py +305 -0
  137. ucloud_sandbox/template/consts.py +30 -0
  138. ucloud_sandbox/template/dockerfile_parser.py +275 -0
  139. ucloud_sandbox/template/logger.py +232 -0
  140. ucloud_sandbox/template/main.py +1323 -0
  141. ucloud_sandbox/template/readycmd.py +138 -0
  142. ucloud_sandbox/template/types.py +105 -0
  143. ucloud_sandbox/template/utils.py +320 -0
  144. ucloud_sandbox/template_async/build_api.py +202 -0
  145. ucloud_sandbox/template_async/main.py +366 -0
  146. ucloud_sandbox/template_sync/build_api.py +199 -0
  147. ucloud_sandbox/template_sync/main.py +371 -0
  148. ucloud_sandbox-0.1.0b3.dist-info/METADATA +77 -0
  149. ucloud_sandbox-0.1.0b3.dist-info/RECORD +151 -0
  150. ucloud_sandbox-0.1.0b3.dist-info/WHEEL +4 -0
  151. ucloud_sandbox-0.1.0b3.dist-info/licenses/LICENSE +12 -0
@@ -0,0 +1 @@
1
+ from .client import Client, GzipCompressor, ConnectException, Code # noqa: F401
e2b_connect/client.py ADDED
@@ -0,0 +1,493 @@
1
+ import gzip
2
+ import inspect
3
+ import json
4
+ import struct
5
+ import typing
6
+
7
+ from httpcore import (
8
+ ConnectionPool,
9
+ AsyncConnectionPool,
10
+ RemoteProtocolError,
11
+ Response,
12
+ )
13
+ from enum import Flag, Enum
14
+ from typing import Callable, Optional, Dict, Any, Generator, Tuple
15
+ from google.protobuf import json_format
16
+
17
+
18
+ class EnvelopeFlags(Flag):
19
+ compressed = 0b00000001
20
+ end_stream = 0b00000010
21
+
22
+
23
+ class Code(Enum):
24
+ canceled = "canceled"
25
+ unknown = "unknown"
26
+ invalid_argument = "invalid_argument"
27
+ deadline_exceeded = "deadline_exceeded"
28
+ not_found = "not_found"
29
+ already_exists = "already_exists"
30
+ permission_denied = "permission_denied"
31
+ resource_exhausted = "resource_exhausted"
32
+ failed_precondition = "failed_precondition"
33
+ aborted = "aborted"
34
+ out_of_range = "out_of_range"
35
+ unimplemented = "unimplemented"
36
+ internal = "internal"
37
+ unavailable = "unavailable"
38
+ data_loss = "data_loss"
39
+ unauthenticated = "unauthenticated"
40
+
41
+
42
+ def make_error_from_http_code(http_code: int):
43
+ error_code_map = {
44
+ 400: Code.invalid_argument,
45
+ 401: Code.unauthenticated,
46
+ 403: Code.permission_denied,
47
+ 404: Code.not_found,
48
+ 409: Code.already_exists,
49
+ 413: Code.resource_exhausted,
50
+ 429: Code.resource_exhausted,
51
+ 499: Code.canceled,
52
+ 500: Code.internal,
53
+ 501: Code.unimplemented,
54
+ 502: Code.unavailable,
55
+ 503: Code.unavailable,
56
+ 504: Code.deadline_exceeded,
57
+ 505: Code.unimplemented,
58
+ }
59
+
60
+ return error_code_map.get(http_code, Code.unknown)
61
+
62
+
63
+ class ConnectException(Exception):
64
+ def __init__(self, status: Code, message: str):
65
+ self.status = status
66
+ self.message = message
67
+
68
+
69
+ envelope_header_length = 5
70
+ envelope_header_pack = ">BI"
71
+
72
+
73
+ def encode_envelope(*, flags: EnvelopeFlags, data):
74
+ return encode_envelope_header(flags=flags.value, data=data) + data
75
+
76
+
77
+ def encode_envelope_header(*, flags, data):
78
+ return struct.pack(envelope_header_pack, flags, len(data))
79
+
80
+
81
+ def decode_envelope_header(header):
82
+ flags, data_len = struct.unpack(envelope_header_pack, header)
83
+ return EnvelopeFlags(flags), data_len
84
+
85
+
86
+ def error_for_response(http_resp: Response):
87
+ try:
88
+ error = json.loads(http_resp.content)
89
+ return make_error(error)
90
+ except (json.decoder.JSONDecodeError, KeyError):
91
+ error = {"code": http_resp.status, "message": http_resp.content.decode("utf-8")}
92
+ return make_error(error)
93
+
94
+
95
+ def make_error(error):
96
+ status = None
97
+ try:
98
+ code_value = error.get("code")
99
+ # return error code from http status code
100
+ if isinstance(code_value, int):
101
+ status = make_error_from_http_code(code_value)
102
+ else:
103
+ status = Code(code_value)
104
+ except (KeyError, ValueError):
105
+ status = Code.unknown
106
+
107
+ return ConnectException(status, error.get("message", ""))
108
+
109
+
110
+ def _sync_retry(func, exc, retries):
111
+ def retry(*args, **kwargs):
112
+ for _ in range(retries):
113
+ try:
114
+ return func(*args, **kwargs)
115
+ except exc:
116
+ continue
117
+
118
+ return func(*args, **kwargs)
119
+
120
+ return retry
121
+
122
+
123
+ def _async_retry(func, exc, retries):
124
+ async def retry(*args, **kwargs):
125
+ for _ in range(retries):
126
+ try:
127
+ return await func(*args, **kwargs)
128
+ except exc:
129
+ continue
130
+
131
+ return await func(*args, **kwargs)
132
+
133
+ return retry
134
+
135
+
136
+ def _retry(exc: typing.Type[Exception], retries: int):
137
+ def decorator(func):
138
+ if inspect.iscoroutinefunction(func):
139
+ return _async_retry(func, exc, retries)
140
+
141
+ return _sync_retry(func, exc, retries)
142
+
143
+ return decorator
144
+
145
+
146
+ class GzipCompressor:
147
+ name = "gzip"
148
+ decompress = gzip.decompress
149
+ compress = gzip.compress
150
+
151
+
152
+ class JSONCodec:
153
+ content_type = "json"
154
+
155
+ @staticmethod
156
+ def encode(msg):
157
+ return json_format.MessageToJson(msg).encode("utf8")
158
+
159
+ @staticmethod
160
+ def decode(data, *, msg_type):
161
+ msg = msg_type()
162
+ json_format.Parse(data.decode("utf8"), msg, ignore_unknown_fields=True)
163
+ return msg
164
+
165
+
166
+ class ProtobufCodec:
167
+ content_type = "proto"
168
+
169
+ @staticmethod
170
+ def encode(msg):
171
+ return msg.SerializeToString()
172
+
173
+ @staticmethod
174
+ def decode(data, *, msg_type):
175
+ msg = msg_type()
176
+ msg.ParseFromString(data)
177
+ return msg
178
+
179
+
180
+ class Client:
181
+ def __init__(
182
+ self,
183
+ *,
184
+ pool: Optional[ConnectionPool] = None,
185
+ async_pool: Optional[AsyncConnectionPool] = None,
186
+ url: str,
187
+ response_type,
188
+ compressor=None,
189
+ json: Optional[bool] = False,
190
+ headers: Optional[Dict[str, str]] = None,
191
+ ):
192
+ if headers is None:
193
+ headers = {}
194
+
195
+ self.pool = pool
196
+ self.async_pool = async_pool
197
+ self.url = url
198
+ self._codec = JSONCodec if json else ProtobufCodec
199
+ self._response_type = response_type
200
+ self._compressor = compressor
201
+ self._headers = headers
202
+ self._connection_retries = 3
203
+
204
+ def _prepare_unary_request(
205
+ self,
206
+ req,
207
+ request_timeout=None,
208
+ headers: Optional[dict] = None,
209
+ **opts,
210
+ ) -> dict:
211
+ data = self._codec.encode(req)
212
+
213
+ if self._compressor is not None:
214
+ data = self._compressor.compress(data)
215
+
216
+ if headers is None:
217
+ headers = {}
218
+
219
+ extensions = (
220
+ None
221
+ if request_timeout is None
222
+ else {
223
+ "timeout": {
224
+ "connect": request_timeout,
225
+ "pool": request_timeout,
226
+ "read": request_timeout,
227
+ "write": request_timeout,
228
+ }
229
+ }
230
+ )
231
+
232
+ return {
233
+ "method": "POST",
234
+ "url": self.url,
235
+ "content": data,
236
+ "extensions": extensions,
237
+ "headers": {
238
+ **self._headers,
239
+ **headers,
240
+ **opts.get("headers", {}),
241
+ "connect-protocol-version": "1",
242
+ "content-encoding": (
243
+ "identity" if self._compressor is None else self._compressor.name
244
+ ),
245
+ "content-type": f"application/{self._codec.content_type}",
246
+ },
247
+ }
248
+
249
+ def _process_unary_response(
250
+ self,
251
+ http_resp: Response,
252
+ ):
253
+ if http_resp.status != 200:
254
+ raise error_for_response(http_resp)
255
+
256
+ content = http_resp.content
257
+
258
+ if self._compressor is not None:
259
+ content = self._compressor.decompress(content)
260
+
261
+ return self._codec.decode(
262
+ content,
263
+ msg_type=self._response_type,
264
+ )
265
+
266
+ @_retry(RemoteProtocolError, 3)
267
+ async def acall_unary(
268
+ self,
269
+ req,
270
+ request_timeout=None,
271
+ headers: Optional[dict] = None,
272
+ **opts,
273
+ ):
274
+ if self.async_pool is None:
275
+ raise ValueError("async_pool is required")
276
+
277
+ req_data = self._prepare_unary_request(
278
+ req,
279
+ request_timeout,
280
+ headers,
281
+ **opts,
282
+ )
283
+
284
+ res = await self.async_pool.request(**req_data)
285
+ return self._process_unary_response(res)
286
+
287
+ @_retry(RemoteProtocolError, 3)
288
+ def call_unary(
289
+ self,
290
+ req,
291
+ request_timeout=None,
292
+ headers: Optional[dict] = None,
293
+ **opts,
294
+ ):
295
+ if self.pool is None:
296
+ raise ValueError("pool is required")
297
+
298
+ req_data = self._prepare_unary_request(
299
+ req,
300
+ request_timeout,
301
+ headers,
302
+ **opts,
303
+ )
304
+
305
+ res = self.pool.request(**req_data)
306
+ return self._process_unary_response(res)
307
+
308
+ def _create_stream_timeout(self, timeout: Optional[int]):
309
+ if timeout:
310
+ return {"connect-timeout-ms": str(timeout * 1000)}
311
+ return {}
312
+
313
+ def _prepare_server_stream_request(
314
+ self,
315
+ req,
316
+ request_timeout=None,
317
+ timeout=None,
318
+ headers: Optional[dict] = None,
319
+ **opts,
320
+ ) -> dict:
321
+ headers = headers or {}
322
+ data = self._codec.encode(req)
323
+ flags = EnvelopeFlags(0)
324
+
325
+ extensions = (
326
+ None
327
+ if request_timeout is None
328
+ else {"timeout": {"connect": request_timeout, "pool": request_timeout}}
329
+ )
330
+
331
+ if self._compressor is not None:
332
+ data = self._compressor.compress(data)
333
+ flags |= EnvelopeFlags.compressed
334
+
335
+ stream_timeout = self._create_stream_timeout(timeout)
336
+
337
+ return {
338
+ "method": "POST",
339
+ "url": self.url,
340
+ "content": encode_envelope(
341
+ flags=flags,
342
+ data=data,
343
+ ),
344
+ "extensions": extensions,
345
+ "headers": {
346
+ **self._headers,
347
+ **headers,
348
+ **opts.get("headers", {}),
349
+ **stream_timeout,
350
+ "connect-protocol-version": "1",
351
+ "connect-content-encoding": (
352
+ "identity" if self._compressor is None else self._compressor.name
353
+ ),
354
+ "content-type": f"application/connect+{self._codec.content_type}",
355
+ },
356
+ }
357
+
358
+ @_retry(RemoteProtocolError, 3)
359
+ async def acall_server_stream(
360
+ self,
361
+ req,
362
+ request_timeout=None,
363
+ timeout=None,
364
+ headers: Optional[dict] = None,
365
+ **opts,
366
+ ):
367
+ if self.async_pool is None:
368
+ raise ValueError("async_pool is required")
369
+
370
+ req_data = self._prepare_server_stream_request(
371
+ req,
372
+ request_timeout,
373
+ timeout,
374
+ headers,
375
+ **opts,
376
+ )
377
+
378
+ parser = ServerStreamParser(
379
+ decode=self._codec.decode,
380
+ response_type=self._response_type,
381
+ )
382
+
383
+ async with self.async_pool.stream(**req_data) as http_resp:
384
+ if http_resp.status != 200:
385
+ await http_resp.aread()
386
+ raise error_for_response(http_resp)
387
+
388
+ async for chunk in http_resp.aiter_stream():
389
+ for parsed in parser.parse(chunk):
390
+ yield parsed
391
+
392
+ @_retry(RemoteProtocolError, 3)
393
+ def call_server_stream(
394
+ self,
395
+ req,
396
+ request_timeout=None,
397
+ timeout=None,
398
+ headers: Optional[dict] = None,
399
+ **opts,
400
+ ):
401
+ if self.pool is None:
402
+ raise ValueError("pool is required")
403
+
404
+ req_data = self._prepare_server_stream_request(
405
+ req,
406
+ request_timeout,
407
+ timeout,
408
+ headers,
409
+ **opts,
410
+ )
411
+
412
+ parser = ServerStreamParser(
413
+ decode=self._codec.decode,
414
+ response_type=self._response_type,
415
+ )
416
+
417
+ with self.pool.stream(**req_data) as http_resp:
418
+ if http_resp.status != 200:
419
+ http_resp.read()
420
+ raise error_for_response(http_resp)
421
+
422
+ for chunk in http_resp.iter_stream():
423
+ for parsed in parser.parse(chunk):
424
+ yield parsed
425
+
426
+ def call_client_stream(self, req, **opts):
427
+ raise NotImplementedError("client stream not supported")
428
+
429
+ def acall_client_stream(self, req, **opts):
430
+ raise NotImplementedError("client stream not supported")
431
+
432
+ def call_bidi_stream(self, req, **opts):
433
+ raise NotImplementedError("bidi stream not supported")
434
+
435
+ def acall_bidi_stream(self, req, **opts):
436
+ raise NotImplementedError("bidi stream not supported")
437
+
438
+
439
+ DataLen = int
440
+
441
+
442
+ class ServerStreamParser:
443
+ def __init__(
444
+ self,
445
+ decode: Callable,
446
+ response_type: Any,
447
+ ):
448
+ self.decode = decode
449
+ self.response_type = response_type
450
+
451
+ self.buffer: bytes = b""
452
+ self._header: Optional[tuple[EnvelopeFlags, DataLen]] = None
453
+
454
+ def shift_buffer(self, size: int):
455
+ buffer = self.buffer[:size]
456
+ self.buffer = self.buffer[size:]
457
+ return buffer
458
+
459
+ @property
460
+ def header(self) -> Tuple[EnvelopeFlags, DataLen]:
461
+ if self._header:
462
+ return self._header
463
+
464
+ header_data = self.shift_buffer(envelope_header_length)
465
+ self._header = decode_envelope_header(header_data)
466
+
467
+ return self._header
468
+
469
+ @header.deleter
470
+ def header(self):
471
+ self._header = None
472
+
473
+ def parse(self, chunk: bytes) -> Generator[Any, None, None]:
474
+ self.buffer += chunk
475
+
476
+ while len(self.buffer) >= envelope_header_length:
477
+ flags, data_len = self.header
478
+
479
+ if data_len > len(self.buffer):
480
+ break
481
+
482
+ data = self.shift_buffer(data_len)
483
+
484
+ if EnvelopeFlags.end_stream in flags:
485
+ data = json.loads(data)
486
+
487
+ if "error" in data:
488
+ raise make_error(data["error"])
489
+
490
+ return
491
+
492
+ yield self.decode(data, msg_type=self.response_type)
493
+ del self.header
@@ -0,0 +1,164 @@
1
+ """
2
+ UCloud AgentBox SDK - Secure sandboxed cloud environments for AI agents.
3
+
4
+ AgentBox is a secure cloud sandbox environment made for AI agents and AI apps.
5
+ Sandboxes allow AI agents and apps to have long running cloud secure environments.
6
+ In these environments, large language models can use the same tools as humans do.
7
+
8
+ This SDK supports both sync and async API:
9
+
10
+ ```py
11
+ from ucloud_agentbox import Sandbox
12
+
13
+ # Create sandbox
14
+ sandbox = Sandbox.create()
15
+ ```
16
+
17
+ ```py
18
+ from ucloud_agentbox import AsyncSandbox
19
+
20
+ # Create sandbox
21
+ sandbox = await AsyncSandbox.create()
22
+ ```
23
+ """
24
+
25
+ from .api import (
26
+ ApiClient,
27
+ client,
28
+ )
29
+ from .connection_config import (
30
+ ConnectionConfig,
31
+ ProxyTypes,
32
+ )
33
+ from .exceptions import (
34
+ AuthenticationException,
35
+ BuildException,
36
+ FileUploadException,
37
+ InvalidArgumentException,
38
+ NotEnoughSpaceException,
39
+ NotFoundException,
40
+ SandboxException,
41
+ TemplateException,
42
+ TimeoutException,
43
+ )
44
+ from .sandbox.commands.command_handle import (
45
+ CommandExitException,
46
+ CommandResult,
47
+ PtyOutput,
48
+ PtySize,
49
+ Stderr,
50
+ Stdout,
51
+ )
52
+ from .sandbox.commands.main import ProcessInfo
53
+ from .sandbox.filesystem.filesystem import EntryInfo, FileType, WriteInfo
54
+ from .sandbox.filesystem.watch_handle import (
55
+ FilesystemEvent,
56
+ FilesystemEventType,
57
+ )
58
+ from .sandbox.network import ALL_TRAFFIC
59
+ from .sandbox.sandbox_api import (
60
+ SandboxInfo,
61
+ SandboxMetrics,
62
+ SandboxNetworkOpts,
63
+ SandboxQuery,
64
+ SandboxState,
65
+ )
66
+ from .sandbox_async.commands.command_handle import AsyncCommandHandle
67
+ from .sandbox_async.filesystem.watch_handle import AsyncWatchHandle
68
+ from .sandbox_async.main import AsyncSandbox
69
+ from .sandbox_async.paginator import AsyncSandboxPaginator
70
+ from .sandbox_async.utils import OutputHandler
71
+ from .sandbox_sync.commands.command_handle import CommandHandle
72
+ from .sandbox_sync.filesystem.watch_handle import WatchHandle
73
+ from .sandbox_sync.main import Sandbox
74
+ from .sandbox_sync.paginator import SandboxPaginator
75
+ from .template.logger import (
76
+ LogEntry,
77
+ LogEntryEnd,
78
+ LogEntryLevel,
79
+ LogEntryStart,
80
+ default_build_logger,
81
+ )
82
+ from .template.main import TemplateBase, TemplateClass
83
+ from .template.readycmd import (
84
+ ReadyCmd,
85
+ wait_for_file,
86
+ wait_for_port,
87
+ wait_for_process,
88
+ wait_for_timeout,
89
+ wait_for_url,
90
+ )
91
+ from .template.types import BuildInfo, CopyItem
92
+ from .template_async.main import AsyncTemplate
93
+ from .template_sync.main import Template
94
+
95
+ __all__ = [
96
+ # API
97
+ "ApiClient",
98
+ "client",
99
+ # Connection config
100
+ "ConnectionConfig",
101
+ "ProxyTypes",
102
+ # Exceptions
103
+ "SandboxException",
104
+ "TimeoutException",
105
+ "NotFoundException",
106
+ "AuthenticationException",
107
+ "InvalidArgumentException",
108
+ "NotEnoughSpaceException",
109
+ "TemplateException",
110
+ "BuildException",
111
+ "FileUploadException",
112
+ # Sandbox API
113
+ "SandboxInfo",
114
+ "SandboxMetrics",
115
+ "ProcessInfo",
116
+ "SandboxQuery",
117
+ "SandboxState",
118
+ "SandboxMetrics",
119
+ # Command handle
120
+ "CommandResult",
121
+ "Stderr",
122
+ "Stdout",
123
+ "CommandExitException",
124
+ "PtyOutput",
125
+ "PtySize",
126
+ # Filesystem
127
+ "FilesystemEvent",
128
+ "FilesystemEventType",
129
+ "EntryInfo",
130
+ "WriteInfo",
131
+ "FileType",
132
+ # Network
133
+ "SandboxNetworkOpts",
134
+ "ALL_TRAFFIC",
135
+ # Sync sandbox
136
+ "Sandbox",
137
+ "SandboxPaginator",
138
+ "WatchHandle",
139
+ "CommandHandle",
140
+ # Async sandbox
141
+ "OutputHandler",
142
+ "AsyncSandboxPaginator",
143
+ "AsyncSandbox",
144
+ "AsyncWatchHandle",
145
+ "AsyncCommandHandle",
146
+ # Template
147
+ "Template",
148
+ "AsyncTemplate",
149
+ "TemplateBase",
150
+ "TemplateClass",
151
+ "CopyItem",
152
+ "BuildInfo",
153
+ "ReadyCmd",
154
+ "wait_for_file",
155
+ "wait_for_url",
156
+ "wait_for_port",
157
+ "wait_for_process",
158
+ "wait_for_timeout",
159
+ "LogEntry",
160
+ "LogEntryStart",
161
+ "LogEntryEnd",
162
+ "LogEntryLevel",
163
+ "default_build_logger",
164
+ ]