agentrun-inner-test 0.0.46__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 (135) hide show
  1. agentrun/__init__.py +325 -0
  2. agentrun/agent_runtime/__client_async_template.py +466 -0
  3. agentrun/agent_runtime/__endpoint_async_template.py +345 -0
  4. agentrun/agent_runtime/__init__.py +53 -0
  5. agentrun/agent_runtime/__runtime_async_template.py +477 -0
  6. agentrun/agent_runtime/api/__data_async_template.py +58 -0
  7. agentrun/agent_runtime/api/__init__.py +6 -0
  8. agentrun/agent_runtime/api/control.py +1362 -0
  9. agentrun/agent_runtime/api/data.py +98 -0
  10. agentrun/agent_runtime/client.py +868 -0
  11. agentrun/agent_runtime/endpoint.py +649 -0
  12. agentrun/agent_runtime/model.py +362 -0
  13. agentrun/agent_runtime/runtime.py +904 -0
  14. agentrun/credential/__client_async_template.py +177 -0
  15. agentrun/credential/__credential_async_template.py +216 -0
  16. agentrun/credential/__init__.py +28 -0
  17. agentrun/credential/api/__init__.py +5 -0
  18. agentrun/credential/api/control.py +606 -0
  19. agentrun/credential/client.py +319 -0
  20. agentrun/credential/credential.py +381 -0
  21. agentrun/credential/model.py +248 -0
  22. agentrun/integration/__init__.py +21 -0
  23. agentrun/integration/agentscope/__init__.py +12 -0
  24. agentrun/integration/agentscope/adapter.py +17 -0
  25. agentrun/integration/agentscope/builtin.py +65 -0
  26. agentrun/integration/agentscope/message_adapter.py +185 -0
  27. agentrun/integration/agentscope/model_adapter.py +60 -0
  28. agentrun/integration/agentscope/tool_adapter.py +59 -0
  29. agentrun/integration/builtin/__init__.py +16 -0
  30. agentrun/integration/builtin/model.py +93 -0
  31. agentrun/integration/builtin/sandbox.py +1234 -0
  32. agentrun/integration/builtin/toolset.py +47 -0
  33. agentrun/integration/crewai/__init__.py +12 -0
  34. agentrun/integration/crewai/adapter.py +9 -0
  35. agentrun/integration/crewai/builtin.py +65 -0
  36. agentrun/integration/crewai/model_adapter.py +31 -0
  37. agentrun/integration/crewai/tool_adapter.py +26 -0
  38. agentrun/integration/google_adk/__init__.py +12 -0
  39. agentrun/integration/google_adk/adapter.py +15 -0
  40. agentrun/integration/google_adk/builtin.py +65 -0
  41. agentrun/integration/google_adk/message_adapter.py +144 -0
  42. agentrun/integration/google_adk/model_adapter.py +46 -0
  43. agentrun/integration/google_adk/tool_adapter.py +235 -0
  44. agentrun/integration/langchain/__init__.py +30 -0
  45. agentrun/integration/langchain/adapter.py +15 -0
  46. agentrun/integration/langchain/builtin.py +71 -0
  47. agentrun/integration/langchain/message_adapter.py +141 -0
  48. agentrun/integration/langchain/model_adapter.py +37 -0
  49. agentrun/integration/langchain/tool_adapter.py +50 -0
  50. agentrun/integration/langgraph/__init__.py +35 -0
  51. agentrun/integration/langgraph/adapter.py +20 -0
  52. agentrun/integration/langgraph/agent_converter.py +1073 -0
  53. agentrun/integration/langgraph/builtin.py +65 -0
  54. agentrun/integration/pydantic_ai/__init__.py +12 -0
  55. agentrun/integration/pydantic_ai/adapter.py +13 -0
  56. agentrun/integration/pydantic_ai/builtin.py +65 -0
  57. agentrun/integration/pydantic_ai/model_adapter.py +44 -0
  58. agentrun/integration/pydantic_ai/tool_adapter.py +19 -0
  59. agentrun/integration/utils/__init__.py +112 -0
  60. agentrun/integration/utils/adapter.py +560 -0
  61. agentrun/integration/utils/canonical.py +164 -0
  62. agentrun/integration/utils/converter.py +134 -0
  63. agentrun/integration/utils/model.py +110 -0
  64. agentrun/integration/utils/tool.py +1759 -0
  65. agentrun/model/__client_async_template.py +357 -0
  66. agentrun/model/__init__.py +57 -0
  67. agentrun/model/__model_proxy_async_template.py +270 -0
  68. agentrun/model/__model_service_async_template.py +267 -0
  69. agentrun/model/api/__init__.py +6 -0
  70. agentrun/model/api/control.py +1173 -0
  71. agentrun/model/api/data.py +196 -0
  72. agentrun/model/client.py +674 -0
  73. agentrun/model/model.py +235 -0
  74. agentrun/model/model_proxy.py +439 -0
  75. agentrun/model/model_service.py +438 -0
  76. agentrun/sandbox/__aio_sandbox_async_template.py +523 -0
  77. agentrun/sandbox/__browser_sandbox_async_template.py +110 -0
  78. agentrun/sandbox/__client_async_template.py +491 -0
  79. agentrun/sandbox/__code_interpreter_sandbox_async_template.py +463 -0
  80. agentrun/sandbox/__init__.py +69 -0
  81. agentrun/sandbox/__sandbox_async_template.py +463 -0
  82. agentrun/sandbox/__template_async_template.py +152 -0
  83. agentrun/sandbox/aio_sandbox.py +905 -0
  84. agentrun/sandbox/api/__aio_data_async_template.py +335 -0
  85. agentrun/sandbox/api/__browser_data_async_template.py +140 -0
  86. agentrun/sandbox/api/__code_interpreter_data_async_template.py +206 -0
  87. agentrun/sandbox/api/__init__.py +19 -0
  88. agentrun/sandbox/api/__sandbox_data_async_template.py +107 -0
  89. agentrun/sandbox/api/aio_data.py +551 -0
  90. agentrun/sandbox/api/browser_data.py +172 -0
  91. agentrun/sandbox/api/code_interpreter_data.py +396 -0
  92. agentrun/sandbox/api/control.py +1051 -0
  93. agentrun/sandbox/api/playwright_async.py +492 -0
  94. agentrun/sandbox/api/playwright_sync.py +492 -0
  95. agentrun/sandbox/api/sandbox_data.py +154 -0
  96. agentrun/sandbox/browser_sandbox.py +185 -0
  97. agentrun/sandbox/client.py +925 -0
  98. agentrun/sandbox/code_interpreter_sandbox.py +823 -0
  99. agentrun/sandbox/model.py +397 -0
  100. agentrun/sandbox/sandbox.py +848 -0
  101. agentrun/sandbox/template.py +217 -0
  102. agentrun/server/__init__.py +191 -0
  103. agentrun/server/agui_normalizer.py +180 -0
  104. agentrun/server/agui_protocol.py +797 -0
  105. agentrun/server/invoker.py +309 -0
  106. agentrun/server/model.py +427 -0
  107. agentrun/server/openai_protocol.py +535 -0
  108. agentrun/server/protocol.py +140 -0
  109. agentrun/server/server.py +208 -0
  110. agentrun/toolset/__client_async_template.py +62 -0
  111. agentrun/toolset/__init__.py +51 -0
  112. agentrun/toolset/__toolset_async_template.py +204 -0
  113. agentrun/toolset/api/__init__.py +17 -0
  114. agentrun/toolset/api/control.py +262 -0
  115. agentrun/toolset/api/mcp.py +100 -0
  116. agentrun/toolset/api/openapi.py +1251 -0
  117. agentrun/toolset/client.py +102 -0
  118. agentrun/toolset/model.py +321 -0
  119. agentrun/toolset/toolset.py +270 -0
  120. agentrun/utils/__data_api_async_template.py +720 -0
  121. agentrun/utils/__init__.py +5 -0
  122. agentrun/utils/__resource_async_template.py +158 -0
  123. agentrun/utils/config.py +258 -0
  124. agentrun/utils/control_api.py +78 -0
  125. agentrun/utils/data_api.py +1120 -0
  126. agentrun/utils/exception.py +151 -0
  127. agentrun/utils/helper.py +108 -0
  128. agentrun/utils/log.py +77 -0
  129. agentrun/utils/model.py +168 -0
  130. agentrun/utils/resource.py +291 -0
  131. agentrun_inner_test-0.0.46.dist-info/METADATA +263 -0
  132. agentrun_inner_test-0.0.46.dist-info/RECORD +135 -0
  133. agentrun_inner_test-0.0.46.dist-info/WHEEL +5 -0
  134. agentrun_inner_test-0.0.46.dist-info/licenses/LICENSE +201 -0
  135. agentrun_inner_test-0.0.46.dist-info/top_level.txt +1 -0
@@ -0,0 +1,523 @@
1
+ """All-in-One 沙箱高层API模板 / All-in-One Sandbox High-Level API Template
2
+
3
+ 此模板用于生成 All-in-One 沙箱资源的高级API代码,结合了浏览器和代码解释器的功能。
4
+ This template is used to generate high-level API code for All-in-One sandbox resources,
5
+ combining browser and code interpreter capabilities.
6
+ """
7
+
8
+ import asyncio
9
+ import time # noqa: F401
10
+ from typing import Optional
11
+
12
+ from agentrun.sandbox.api.aio_data import AioDataAPI
13
+ from agentrun.sandbox.model import CodeLanguage, TemplateType
14
+ from agentrun.utils.exception import ServerError
15
+ from agentrun.utils.log import logger
16
+
17
+ from .sandbox import Sandbox
18
+
19
+ # ========================================
20
+ # Code Interpreter Helper Classes
21
+ # ========================================
22
+
23
+
24
+ class FileOperations:
25
+ """File upload/download operations."""
26
+
27
+ def __init__(self, sandbox: "AioSandbox"):
28
+ self._sandbox = sandbox
29
+
30
+ async def read_async(self, path: str):
31
+ """Read a file from the sandbox (async).
32
+ Args:
33
+ path: Remote file path in the sandbox
34
+ Returns:
35
+ File content
36
+ """
37
+ return await self._sandbox.data_api.read_file_async(path=path)
38
+
39
+ async def write_async(
40
+ self,
41
+ path: str,
42
+ content: str,
43
+ mode: str = "644",
44
+ encoding: str = "utf-8",
45
+ create_dir=True,
46
+ ):
47
+ """Write a file to the sandbox (async).
48
+ Args:
49
+ path: Remote file path in the sandbox
50
+ content: File content
51
+ """
52
+ return await self._sandbox.data_api.write_file_async(
53
+ path=path,
54
+ content=content,
55
+ mode=mode,
56
+ encoding=encoding,
57
+ create_dir=create_dir,
58
+ )
59
+
60
+
61
+ class FileSystemOperations:
62
+ """File system operations (list, move, remove, stat, mkdir)."""
63
+
64
+ def __init__(self, sandbox: "AioSandbox"):
65
+ self._sandbox = sandbox
66
+
67
+ async def list_async(
68
+ self, path: Optional[str] = None, depth: Optional[int] = None
69
+ ):
70
+ """List directory contents (async).
71
+
72
+ Args:
73
+ path: Directory path (optional)
74
+ depth: Traversal depth (optional)
75
+
76
+ Returns:
77
+ Directory contents
78
+ """
79
+ return await self._sandbox.data_api.list_directory_async(
80
+ path=path, depth=depth
81
+ )
82
+
83
+ async def move_async(self, source: str, destination: str):
84
+ """Move a file or directory (async).
85
+
86
+ Args:
87
+ source: Source file or directory path
88
+ destination: Target file or directory path
89
+
90
+ Returns:
91
+ Move operation result
92
+ """
93
+ return await self._sandbox.data_api.move_file_async(
94
+ source=source, destination=destination
95
+ )
96
+
97
+ async def remove_async(self, path: str):
98
+ """Remove a file or directory (async).
99
+
100
+ Args:
101
+ path: File or directory path to remove
102
+
103
+ Returns:
104
+ Remove operation result
105
+ """
106
+ return await self._sandbox.data_api.remove_file_async(path=path)
107
+
108
+ async def stat_async(self, path: str):
109
+ """Get file or directory statistics (async).
110
+
111
+ Args:
112
+ path: File or directory path
113
+
114
+ Returns:
115
+ File/directory statistics
116
+ """
117
+ return await self._sandbox.data_api.stat_async(path=path)
118
+
119
+ async def mkdir_async(
120
+ self,
121
+ path: str,
122
+ parents: Optional[bool] = True,
123
+ mode: Optional[str] = "0755",
124
+ ):
125
+ """Create a directory (async).
126
+
127
+ Args:
128
+ path: Directory path to create
129
+ parents: Whether to create parent directories (default: True)
130
+ mode: Directory permissions mode (default: "0755")
131
+
132
+ Returns:
133
+ Mkdir operation result
134
+ """
135
+ return await self._sandbox.data_api.mkdir_async(
136
+ path=path, parents=parents, mode=mode
137
+ )
138
+
139
+ async def upload_async(
140
+ self,
141
+ local_file_path: str,
142
+ target_file_path: str,
143
+ ):
144
+ """Upload a file to the sandbox (async).
145
+
146
+ Args:
147
+ local_file_path: Local file path to upload
148
+ target_file_path: Target file path in sandbox
149
+
150
+ Returns:
151
+ Upload result
152
+ """
153
+ return await self._sandbox.data_api.upload_file_async(
154
+ local_file_path=local_file_path, target_file_path=target_file_path
155
+ )
156
+
157
+ async def download_async(self, path: str, save_path: str):
158
+ """Download a file from the sandbox (async).
159
+
160
+ Args:
161
+ path: Remote file path in the sandbox
162
+ save_path: Local file path to save the downloaded file
163
+
164
+ Returns:
165
+ Download result with 'saved_path' and 'size'
166
+ """
167
+ return await self._sandbox.data_api.download_file_async(
168
+ path=path, save_path=save_path
169
+ )
170
+
171
+
172
+ class ProcessOperations:
173
+ """Process management operations."""
174
+
175
+ def __init__(self, sandbox: "AioSandbox"):
176
+ self._sandbox = sandbox
177
+
178
+ async def cmd_async(
179
+ self, command: str, cwd: str, timeout: Optional[int] = 30
180
+ ):
181
+ """Execute a command in the sandbox (async).
182
+
183
+ Args:
184
+ command: Command to execute
185
+ cwd: Working directory
186
+ timeout: Execution timeout in seconds (default: 30)
187
+
188
+ Returns:
189
+ Command execution result
190
+ """
191
+ return await self._sandbox.data_api.cmd_async(
192
+ command=command, cwd=cwd, timeout=timeout
193
+ )
194
+
195
+ async def list_async(self):
196
+ """List all processes (async).
197
+
198
+ Returns:
199
+ List of processes
200
+ """
201
+ return await self._sandbox.data_api.list_processes_async()
202
+
203
+ async def get_async(self, pid: str):
204
+ """Get a specific process by PID (async).
205
+
206
+ Args:
207
+ pid: Process ID
208
+
209
+ Returns:
210
+ Process information
211
+ """
212
+ return await self._sandbox.data_api.get_process_async(pid=pid)
213
+
214
+ async def kill_async(self, pid: str):
215
+ """Kill a specific process by PID (async).
216
+
217
+ Args:
218
+ pid: Process ID
219
+
220
+ Returns:
221
+ Kill operation result
222
+ """
223
+ return await self._sandbox.data_api.kill_process_async(pid=pid)
224
+
225
+
226
+ class ContextOperations:
227
+ """Context management operations."""
228
+
229
+ def __init__(self, sandbox: "AioSandbox"):
230
+ self._sandbox = sandbox
231
+ self._context_id: Optional[str] = None
232
+ self._language: Optional[str] = None
233
+ self._cwd: Optional[str] = None
234
+
235
+ @property
236
+ def context_id(self) -> Optional[str]:
237
+ """Get the current context ID."""
238
+ return self._context_id
239
+
240
+ async def list_async(self):
241
+ """List all contexts (async)."""
242
+ return await self._sandbox.data_api.list_contexts_async()
243
+
244
+ async def create_async(
245
+ self,
246
+ language: Optional[CodeLanguage] = CodeLanguage.PYTHON,
247
+ cwd: str = "/home/user",
248
+ ) -> "ContextOperations":
249
+ """Create a new context and save its ID (async).
250
+
251
+ Args:
252
+ language: Programming language (default: "python")
253
+ cwd: Working directory (default: "/home/user")
254
+
255
+ Returns:
256
+ ContextOperations: Returns self for chaining and context manager support
257
+ """
258
+ result = await self._sandbox.data_api.create_context_async(
259
+ language=language, cwd=cwd
260
+ )
261
+ if all(result.get(key) for key in ("id", "cwd", "language")):
262
+ self._context_id = result["id"]
263
+ self._language = result["language"]
264
+ self._cwd = result["cwd"]
265
+ return self
266
+ raise ServerError(500, "Failed to create context")
267
+
268
+ async def get_async(
269
+ self, context_id: Optional[str] = None
270
+ ) -> "ContextOperations":
271
+ """Get a specific context by ID (async).
272
+ Args:
273
+ context_id: Context ID
274
+ Returns:
275
+ ContextOperations: Returns self for chaining and context manager support
276
+ """
277
+ if context_id is None:
278
+ context_id = self._context_id
279
+ if context_id is None:
280
+ logger.error(f"context id is not set")
281
+ raise ValueError("context id is not set,")
282
+ result = await self._sandbox.data_api.get_context_async(
283
+ context_id=context_id
284
+ )
285
+ if all(result.get(key) for key in ("id", "cwd", "language")):
286
+ self._context_id = result["id"]
287
+ self._language = result["language"]
288
+ self._cwd = result["cwd"]
289
+ return self
290
+ raise ServerError(500, "Failed to create context")
291
+
292
+ async def execute_async(
293
+ self,
294
+ code: str,
295
+ language: Optional[CodeLanguage] = None,
296
+ context_id: Optional[str] = None,
297
+ timeout: Optional[int] = 30,
298
+ ):
299
+ """Execute code in a context (async).
300
+
301
+ Args:
302
+ code: Code to execute
303
+ language: Programming language (optional)
304
+ context_id: Context ID (optional, uses saved context_id if not provided)
305
+ timeout: Execution timeout in seconds (default: 30)
306
+
307
+ Returns:
308
+ Execution result
309
+
310
+ Raises:
311
+ ValueError: If no context_id is provided and none is saved
312
+ """
313
+ if context_id is None:
314
+ context_id = self._context_id
315
+ if context_id is None and language is None:
316
+ logger.debug("context id is not set, use default language: python")
317
+ language = CodeLanguage.PYTHON
318
+ return await self._sandbox.data_api.execute_code_async(
319
+ context_id=context_id, language=language, code=code, timeout=timeout
320
+ )
321
+
322
+ async def delete_async(self, context_id: Optional[str] = None):
323
+ """Delete a context (async).
324
+
325
+ Args:
326
+ context_id: Context ID (optional, uses saved context_id if not provided)
327
+
328
+ Returns:
329
+ Delete result
330
+
331
+ Raises:
332
+ ValueError: If no context_id is provided and none is saved
333
+ """
334
+ if context_id is None:
335
+ context_id = self._context_id
336
+ if context_id is None:
337
+ raise ValueError(
338
+ "context_id is required. Either pass it as parameter or create"
339
+ " a context first."
340
+ )
341
+ result = await self._sandbox.data_api.delete_context_async(
342
+ context_id=context_id
343
+ )
344
+ # Clear the saved context_id after deletion
345
+ self._context_id = None
346
+ return result
347
+
348
+ async def __aenter__(self):
349
+ """Asynchronous context manager entry."""
350
+ if self._context_id is None:
351
+ raise ValueError(
352
+ "No context has been created. Call create() first or use: "
353
+ "async with await sandbox.context.create_async(...) as ctx:"
354
+ )
355
+ return self
356
+
357
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
358
+ """Asynchronous context manager exit - deletes the context."""
359
+ if self._context_id is not None:
360
+ try:
361
+ await self._sandbox.data_api.delete_context_async(
362
+ self._context_id
363
+ )
364
+ except Exception as e:
365
+ logger.error(
366
+ f"Warning: Failed to delete context {self._context_id}: {e}"
367
+ )
368
+ return False
369
+
370
+
371
+ class AioSandbox(Sandbox):
372
+ """All-in-One Sandbox combining Browser and Code Interpreter capabilities.
373
+
374
+ This class combines the functionality of BrowserSandbox and CodeInterpreterSandbox,
375
+ providing a unified interface for all-in-one sandbox operations.
376
+ """
377
+
378
+ _template_type = TemplateType.AIO
379
+
380
+ _data_api: Optional["AioDataAPI"] = None
381
+ _file: Optional[FileOperations] = None
382
+ _file_system: Optional[FileSystemOperations] = None
383
+ _context: Optional[ContextOperations] = None
384
+ _process: Optional[ProcessOperations] = None
385
+
386
+ async def __aenter__(self):
387
+ """Asynchronous context manager entry."""
388
+ # Poll health check asynchronously
389
+ max_retries = 60 # Maximum 60 seconds
390
+ retry_count = 0
391
+
392
+ logger.debug("Waiting for All-in-One sandbox to be ready...")
393
+
394
+ while retry_count < max_retries:
395
+ retry_count += 1
396
+
397
+ try:
398
+ health = await self.check_health_async()
399
+
400
+ if health["status"] == "ok":
401
+ logger.debug(
402
+ "✓ All-in-One sandbox is ready! (took"
403
+ f" {retry_count} seconds)"
404
+ )
405
+ return self
406
+
407
+ logger.debug(
408
+ "[%d/%d] Health status: %d %s",
409
+ retry_count,
410
+ max_retries,
411
+ health.get("code", 0),
412
+ health.get("message", "not ready"),
413
+ )
414
+
415
+ except Exception as e:
416
+ logger.error(
417
+ f"[{retry_count}/{max_retries}] Health check failed: {e}"
418
+ )
419
+
420
+ if retry_count < max_retries:
421
+ await asyncio.sleep(1)
422
+
423
+ raise RuntimeError(
424
+ f"Health check timeout after {max_retries} seconds. "
425
+ "All-in-One sandbox did not become ready in time."
426
+ )
427
+
428
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
429
+ """Asynchronous context manager exit."""
430
+ if self.sandbox_id is None:
431
+ raise ValueError("Sandbox ID is not set")
432
+ logger.debug(f"Deleting All-in-One sandbox {self.sandbox_id}...")
433
+ await self.delete_async()
434
+
435
+ @property
436
+ def data_api(self) -> "AioDataAPI":
437
+ """Get data client."""
438
+ if self._data_api is None:
439
+ if self.sandbox_id is None:
440
+ raise ValueError("Sandbox ID is not set")
441
+
442
+ self._data_api = AioDataAPI(
443
+ sandbox_id=self.sandbox_id, config=self._config
444
+ )
445
+
446
+ return self._data_api
447
+
448
+ async def check_health_async(self):
449
+ """Check sandbox health status (async)."""
450
+ return await self.data_api.check_health_async()
451
+
452
+ # ========================================
453
+ # Browser API Methods
454
+ # ========================================
455
+
456
+ def get_cdp_url(self, record: Optional[bool] = False):
457
+ """Get CDP WebSocket URL for browser automation."""
458
+ return self.data_api.get_cdp_url(record=record)
459
+
460
+ def get_vnc_url(self, record: Optional[bool] = False):
461
+ """Get VNC WebSocket URL for live view."""
462
+ return self.data_api.get_vnc_url(record=record)
463
+
464
+ def sync_playwright(self, record: Optional[bool] = False):
465
+ """Get synchronous Playwright browser instance."""
466
+ return self.data_api.sync_playwright(record=record)
467
+
468
+ def async_playwright(self, record: Optional[bool] = False):
469
+ """Get asynchronous Playwright browser instance."""
470
+ return self.data_api.async_playwright(record=record)
471
+
472
+ async def list_recordings_async(self):
473
+ """List all recordings (async)."""
474
+ return await self.data_api.list_recordings_async()
475
+
476
+ async def download_recording_async(self, filename: str, save_path: str):
477
+ """
478
+ Asynchronously download a recording video file and save it to local path.
479
+
480
+ Args:
481
+ filename: The name of the recording file to download
482
+ save_path: Local file path to save the downloaded video file (.mkv)
483
+
484
+ Returns:
485
+ Dictionary with 'saved_path' and 'size' keys
486
+ """
487
+ return await self.data_api.download_recording_async(filename, save_path)
488
+
489
+ async def delete_recording_async(self, filename: str):
490
+ """Delete a recording file (async)."""
491
+ return await self.data_api.delete_recording_async(filename)
492
+
493
+ # ========================================
494
+ # Code Interpreter API Properties
495
+ # ========================================
496
+
497
+ @property
498
+ def file(self) -> FileOperations:
499
+ """Access file upload/download operations."""
500
+ if self._file is None:
501
+ self._file = FileOperations(self)
502
+ return self._file
503
+
504
+ @property
505
+ def file_system(self) -> FileSystemOperations:
506
+ """Access file system operations."""
507
+ if self._file_system is None:
508
+ self._file_system = FileSystemOperations(self)
509
+ return self._file_system
510
+
511
+ @property
512
+ def context(self) -> ContextOperations:
513
+ """Access context management operations."""
514
+ if self._context is None:
515
+ self._context = ContextOperations(self)
516
+ return self._context
517
+
518
+ @property
519
+ def process(self) -> ProcessOperations:
520
+ """Access process management operations."""
521
+ if self._process is None:
522
+ self._process = ProcessOperations(self)
523
+ return self._process
@@ -0,0 +1,110 @@
1
+ """浏览器沙箱高层API模板 / Browser Sandbox High-Level API Template
2
+
3
+ 此模板用于生成浏览器沙箱资源的高级API代码。
4
+ This template is used to generate high-level API code for browser sandbox resources.
5
+ """
6
+
7
+ import asyncio
8
+ import time # noqa: F401
9
+ from typing import Optional
10
+
11
+ from agentrun.sandbox.api import BrowserDataAPI
12
+ from agentrun.sandbox.model import TemplateType
13
+ from agentrun.utils.log import logger
14
+
15
+ from .sandbox import Sandbox
16
+
17
+
18
+ class BrowserSandbox(Sandbox):
19
+ _template_type = TemplateType.BROWSER
20
+
21
+ _data_api: Optional["BrowserDataAPI"] = None
22
+
23
+ async def __aenter__(self):
24
+ # Poll health check asynchronously
25
+ max_retries = 60 # Maximum 60 seconds
26
+ retry_count = 0
27
+
28
+ logger.debug("Waiting for browser to be ready...")
29
+
30
+ while retry_count < max_retries:
31
+ retry_count += 1
32
+
33
+ try:
34
+ health = await self.check_health_async()
35
+
36
+ if health["status"] == "ok":
37
+ logger.debug(
38
+ f"✓ Browser is ready! (took {retry_count} seconds)"
39
+ )
40
+ return self
41
+
42
+ logger.debug(
43
+ f"[{retry_count}/{max_retries}] Health status:"
44
+ f" {health.get('code')} {health.get('message')}",
45
+ )
46
+
47
+ except Exception as e:
48
+ logger.error(
49
+ f"[{retry_count}/{max_retries}] Health check failed: {e}"
50
+ )
51
+
52
+ if retry_count < max_retries:
53
+ await asyncio.sleep(1)
54
+
55
+ raise RuntimeError(
56
+ f"Health check timeout after {max_retries} seconds. "
57
+ "Browser did not become ready in time."
58
+ )
59
+
60
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
61
+ if self.sandbox_id is None:
62
+ raise ValueError("Sandbox ID is not set")
63
+ logger.debug(f"Deleting browser sandbox {self.sandbox_id}...")
64
+ await self.delete_async()
65
+
66
+ @property
67
+ def data_api(self):
68
+ if self._data_api is None:
69
+ if self.sandbox_id is None:
70
+ raise ValueError("Sandbox ID is not set")
71
+
72
+ self._data_api = BrowserDataAPI(
73
+ sandbox_id=self.sandbox_id, config=self._config
74
+ )
75
+
76
+ return self._data_api
77
+
78
+ async def check_health_async(self):
79
+ return await self.data_api.check_health_async()
80
+
81
+ def get_cdp_url(self, record: Optional[bool] = False):
82
+ return self.data_api.get_cdp_url(record=record)
83
+
84
+ def get_vnc_url(self, record: Optional[bool] = False):
85
+ return self.data_api.get_vnc_url(record=record)
86
+
87
+ def sync_playwright(self, record: Optional[bool] = False):
88
+ return self.data_api.sync_playwright(record=record)
89
+
90
+ def async_playwright(self, record: Optional[bool] = False):
91
+ return self.data_api.async_playwright(record=record)
92
+
93
+ async def list_recordings_async(self):
94
+ return await self.data_api.list_recordings_async()
95
+
96
+ async def download_recording_async(self, filename: str, save_path: str):
97
+ """
98
+ Asynchronously download a recording video file and save it to local path.
99
+
100
+ Args:
101
+ filename: The name of the recording file to download
102
+ save_path: Local file path to save the downloaded video file (.mkv)
103
+
104
+ Returns:
105
+ Dictionary with 'saved_path' and 'size' keys
106
+ """
107
+ return await self.data_api.download_recording_async(filename, save_path)
108
+
109
+ async def delete_recording_async(self, filename: str):
110
+ return await self.data_api.delete_recording_async(filename)