agentscope-runtime 0.1.5b1__py3-none-any.whl → 0.2.0b1__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 (106) hide show
  1. agentscope_runtime/common/__init__.py +0 -0
  2. agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
  3. agentscope_runtime/common/collections/redis_mapping.py +42 -0
  4. agentscope_runtime/common/container_clients/__init__.py +0 -0
  5. agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
  6. agentscope_runtime/common/container_clients/docker_client.py +250 -0
  7. agentscope_runtime/engine/__init__.py +12 -0
  8. agentscope_runtime/engine/agents/agentscope_agent.py +488 -0
  9. agentscope_runtime/engine/agents/agno_agent.py +19 -18
  10. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  11. agentscope_runtime/engine/agents/utils.py +53 -0
  12. agentscope_runtime/engine/app/__init__.py +6 -0
  13. agentscope_runtime/engine/app/agent_app.py +239 -0
  14. agentscope_runtime/engine/app/base_app.py +181 -0
  15. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  16. agentscope_runtime/engine/deployers/base.py +1 -0
  17. agentscope_runtime/engine/deployers/cli_fc_deploy.py +72 -12
  18. agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
  19. agentscope_runtime/engine/deployers/local_deployer.py +61 -3
  20. agentscope_runtime/engine/deployers/modelstudio_deployer.py +77 -27
  21. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +3 -3
  22. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
  23. agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
  24. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
  25. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  26. agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
  27. agentscope_runtime/engine/helpers/helper.py +60 -41
  28. agentscope_runtime/engine/runner.py +35 -24
  29. agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
  30. agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
  31. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  32. agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
  33. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  34. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  35. agentscope_runtime/engine/services/utils/__init__.py +0 -0
  36. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  37. agentscope_runtime/engine/tracing/__init__.py +9 -3
  38. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  39. agentscope_runtime/engine/tracing/base.py +66 -34
  40. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  41. agentscope_runtime/engine/tracing/message_util.py +528 -0
  42. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  43. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  44. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  45. agentscope_runtime/sandbox/__init__.py +2 -0
  46. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  47. agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
  48. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  53. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
  54. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  55. agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
  57. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  58. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  59. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  60. agentscope_runtime/sandbox/box/training_box/training_box.py +10 -15
  61. agentscope_runtime/sandbox/build.py +143 -58
  62. agentscope_runtime/sandbox/client/http_client.py +87 -59
  63. agentscope_runtime/sandbox/client/training_client.py +0 -1
  64. agentscope_runtime/sandbox/constant.py +27 -1
  65. agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
  66. agentscope_runtime/sandbox/custom/example.py +4 -3
  67. agentscope_runtime/sandbox/enums.py +1 -0
  68. agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
  69. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  70. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  71. agentscope_runtime/sandbox/model/container.py +12 -23
  72. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  73. agentscope_runtime/sandbox/registry.py +1 -1
  74. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  75. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  76. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  77. agentscope_runtime/sandbox/tools/tool.py +4 -0
  78. agentscope_runtime/sandbox/utils.py +124 -0
  79. agentscope_runtime/version.py +1 -1
  80. {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/METADATA +209 -101
  81. {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/RECORD +95 -79
  82. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  83. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  84. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  85. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  86. agentscope_runtime/engine/llms/__init__.py +0 -3
  87. agentscope_runtime/engine/llms/base_llm.py +0 -60
  88. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  89. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
  90. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
  91. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  92. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
  93. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  94. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  95. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  96. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  97. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  98. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  99. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  100. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  101. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  102. /agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +0 -0
  103. {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/WHEEL +0 -0
  104. {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/entry_points.txt +0 -0
  105. {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
  106. {agentscope_runtime-0.1.5b1.dist-info → agentscope_runtime-0.2.0b1.dist-info}/top_level.txt +0 -0
@@ -3,19 +3,20 @@ import os
3
3
 
4
4
  from typing import Optional
5
5
 
6
- from ..constant import IMAGE_TAG
6
+ from ..utils import build_image_uri
7
7
  from ..registry import SandboxRegistry
8
8
  from ..enums import SandboxType
9
9
  from ..box.sandbox import Sandbox
10
+ from ..constant import TIMEOUT
10
11
 
11
- SANDBOXTYPE = "custom_sandbox"
12
+ SANDBOX_TYPE = "custom_sandbox"
12
13
 
13
14
 
14
15
  @SandboxRegistry.register(
15
- f"agentscope/runtime-sandbox-{SANDBOXTYPE}:{IMAGE_TAG}",
16
- sandbox_type=SANDBOXTYPE,
16
+ build_image_uri(f"runtime-sandbox-{SANDBOX_TYPE}"),
17
+ sandbox_type=SANDBOX_TYPE,
17
18
  security_level="medium",
18
- timeout=60,
19
+ timeout=TIMEOUT,
19
20
  description="my sandbox",
20
21
  environment={
21
22
  "TAVILY_API_KEY": os.getenv("TAVILY_API_KEY", ""),
@@ -35,5 +36,5 @@ class CustomSandbox(Sandbox):
35
36
  timeout,
36
37
  base_url,
37
38
  bearer_token,
38
- SandboxType(SANDBOXTYPE),
39
+ SandboxType(SANDBOX_TYPE),
39
40
  )
@@ -1,19 +1,20 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  from typing import Optional
3
3
 
4
- from ..constant import IMAGE_TAG
4
+ from ..utils import build_image_uri
5
5
  from ..registry import SandboxRegistry
6
6
  from ..enums import SandboxType
7
7
  from ..box.sandbox import Sandbox
8
+ from ..constant import TIMEOUT
8
9
 
9
10
  SANDBOX_TYPE = "example"
10
11
 
11
12
 
12
13
  @SandboxRegistry.register(
13
- f"agentscope/runtime-sandbox-{SANDBOX_TYPE}:{IMAGE_TAG}",
14
+ build_image_uri(f"runtime-sandbox-{SANDBOX_TYPE}"),
14
15
  sandbox_type=SANDBOX_TYPE,
15
16
  security_level="medium",
16
- timeout=60,
17
+ timeout=TIMEOUT,
17
18
  description="Example sandbox",
18
19
  )
19
20
  class ExampleSandbox(Sandbox):
@@ -65,6 +65,7 @@ class SandboxType(DynamicEnum):
65
65
  BASE = "base"
66
66
  BROWSER = "browser"
67
67
  FILESYSTEM = "filesystem"
68
+ GUI = "gui"
68
69
  APPWORLD = "appworld"
69
70
  BFCL = "bfcl"
70
71
  WEBSHOP = "webshop"
@@ -1,39 +1,36 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # pylint: disable=redefined-outer-name, protected-access
3
+ # pylint: disable=too-many-branches, too-many-statements
2
4
  # pylint: disable=redefined-outer-name, protected-access, too-many-branches
5
+ import inspect
6
+ import json
3
7
  import logging
4
8
  import os
5
- import json
6
9
  import secrets
7
- import inspect
8
10
  import traceback
9
-
10
11
  from functools import wraps
11
- from typing import Optional, Dict
12
- from urllib.parse import urlparse, urlunparse
12
+ from typing import Optional, Dict, Union, List
13
13
 
14
- import shortuuid
15
14
  import requests
15
+ import shortuuid
16
16
 
17
+ from ..client import SandboxHttpClient, TrainingSandboxClient
18
+ from ..enums import SandboxType
19
+ from ..manager.storage import (
20
+ LocalStorage,
21
+ OSSStorage,
22
+ )
17
23
  from ..model import (
18
24
  ContainerModel,
19
25
  SandboxManagerEnvConfig,
20
26
  )
21
- from ..enums import SandboxType
22
27
  from ..registry import SandboxRegistry
23
- from ..client import SandboxHttpClient, TrainingSandboxClient
24
-
25
- from ..manager.collections import (
28
+ from ...common.collections import (
26
29
  RedisMapping,
27
30
  RedisQueue,
28
31
  InMemoryMapping,
29
32
  InMemoryQueue,
30
33
  )
31
- from ..manager.storage import (
32
- LocalStorage,
33
- OSSStorage,
34
- )
35
- from ..manager.container_clients import DockerClient, KubernetesClient
36
- from ..constant import BROWSER_SESSION_ID
37
34
 
38
35
  logging.basicConfig(level=logging.INFO)
39
36
  logger = logging.getLogger(__name__)
@@ -85,7 +82,11 @@ class SandboxManager:
85
82
  config: Optional[SandboxManagerEnvConfig] = None,
86
83
  base_url=None,
87
84
  bearer_token=None,
88
- default_type: SandboxType | str = SandboxType.BASE,
85
+ default_type: Union[
86
+ SandboxType,
87
+ str,
88
+ List[Union[SandboxType, str]],
89
+ ] = SandboxType.BASE,
89
90
  ):
90
91
  if base_url:
91
92
  # Initialize HTTP session for remote mode with bearer token
@@ -112,17 +113,24 @@ class SandboxManager:
112
113
  default_mount_dir="sessions_mount_dir",
113
114
  )
114
115
 
115
- self.default_type = SandboxType(default_type)
116
+ # Support multi sandbox pool
117
+ if isinstance(default_type, (SandboxType, str)):
118
+ self.default_type = [SandboxType(default_type)]
119
+ else:
120
+ self.default_type = [SandboxType(x) for x in list(default_type)]
121
+
116
122
  self.workdir = "/workspace"
117
123
 
118
124
  self.config = config
119
125
  self.pool_size = self.config.pool_size
120
126
  self.prefix = self.config.container_prefix_key
121
127
  self.default_mount_dir = self.config.default_mount_dir
128
+ self.readonly_mounts = self.config.readonly_mounts
122
129
  self.storage_folder = (
123
130
  self.config.storage_folder or self.default_mount_dir
124
131
  )
125
132
 
133
+ self.pool_queues = {}
126
134
  if self.config.redis_enabled:
127
135
  import redis
128
136
 
@@ -142,21 +150,44 @@ class SandboxManager:
142
150
  ) from e
143
151
 
144
152
  self.container_mapping = RedisMapping(redis_client)
145
- self.pool_queue = RedisQueue(
153
+ self.session_mapping = RedisMapping(
146
154
  redis_client,
147
- self.config.redis_container_pool_key,
155
+ prefix="session_mapping",
148
156
  )
157
+
158
+ # Init multi sand box pool
159
+ for t in self.default_type:
160
+ queue_key = f"{self.config.redis_container_pool_key}:{t.value}"
161
+ self.pool_queues[t] = RedisQueue(redis_client, queue_key)
149
162
  else:
150
163
  self.container_mapping = InMemoryMapping()
151
- self.pool_queue = InMemoryQueue()
164
+ self.session_mapping = InMemoryMapping()
165
+
166
+ # Init multi sand box pool
167
+ for t in self.default_type:
168
+ self.pool_queues[t] = InMemoryQueue()
152
169
 
153
170
  self.container_deployment = self.config.container_deployment
154
171
 
155
172
  if base_url is None:
156
173
  if self.container_deployment == "docker":
174
+ from ...common.container_clients.docker_client import (
175
+ DockerClient,
176
+ )
177
+
157
178
  self.client = DockerClient(config=self.config)
158
179
  elif self.container_deployment == "k8s":
180
+ from ...common.container_clients.kubernetes_client import (
181
+ KubernetesClient,
182
+ )
183
+
159
184
  self.client = KubernetesClient(config=self.config)
185
+ elif self.container_deployment == "agentrun":
186
+ from ...common.container_clients.agentrun_client import (
187
+ AgentRunClient,
188
+ )
189
+
190
+ self.client = AgentRunClient(config=self.config)
160
191
  else:
161
192
  raise NotImplementedError("Not implemented")
162
193
  else:
@@ -243,24 +274,28 @@ class SandboxManager:
243
274
  """
244
275
  Init runtime pool
245
276
  """
246
- while self.pool_queue.size() < self.pool_size:
247
- try:
248
- container_name = self.create()
249
- container_model = self.container_mapping.get(container_name)
250
- if container_model:
251
- # Check the pool size again to avoid race condition
252
- if self.pool_queue.size() < self.pool_size:
253
- self.pool_queue.enqueue(container_model)
277
+ for t in self.default_type:
278
+ queue = self.pool_queues[t]
279
+ while queue.size() < self.pool_size:
280
+ try:
281
+ container_name = self.create(sandbox_type=t.value)
282
+ container_model = self.container_mapping.get(
283
+ container_name,
284
+ )
285
+ if container_model:
286
+ # Check the pool size again to avoid race condition
287
+ if queue.size() < self.pool_size:
288
+ queue.enqueue(container_model)
289
+ else:
290
+ # The pool size has reached the limit
291
+ self.release(container_name)
292
+ break
254
293
  else:
255
- # The pool size has reached the limit
256
- self.release(container_name)
294
+ logger.error("Failed to create container for pool")
257
295
  break
258
- else:
259
- logger.error("Failed to create container for pool")
296
+ except Exception as e:
297
+ logger.error(f"Error initializing runtime pool: {e}")
260
298
  break
261
- except Exception as e:
262
- logger.error(f"Error initializing runtime pool: {e}")
263
- break
264
299
 
265
300
  @remote_wrapper()
266
301
  def cleanup(self):
@@ -269,17 +304,19 @@ class SandboxManager:
269
304
  )
270
305
 
271
306
  # Clean up pool first
272
- try:
273
- while self.pool_queue.size() > 0:
274
- container_json = self.pool_queue.dequeue()
275
- if container_json:
276
- container_model = ContainerModel(**container_json)
277
- logger.debug(
278
- f"Destroy container {container_model.container_id}",
279
- )
280
- self.release(container_model.session_id)
281
- except Exception as e:
282
- logger.error(f"Error cleaning up runtime pool: {e}")
307
+ for queue in self.pool_queues.values():
308
+ try:
309
+ while queue.size() > 0:
310
+ container_json = queue.dequeue()
311
+ if container_json:
312
+ container_model = ContainerModel(**container_json)
313
+ logger.debug(
314
+ f"Destroy container"
315
+ f" {container_model.container_id}",
316
+ )
317
+ self.release(container_model.session_id)
318
+ except Exception as e:
319
+ logger.error(f"Error cleaning up runtime pool: {e}")
283
320
 
284
321
  # Clean up rest container
285
322
  for key in self.container_mapping.scan(self.prefix):
@@ -297,11 +334,15 @@ class SandboxManager:
297
334
  )
298
335
 
299
336
  @remote_wrapper()
300
- def create_from_pool(self, sandbox_type=None):
337
+ def create_from_pool(self, sandbox_type=None, meta: Optional[Dict] = None):
301
338
  """Try to get a container from runtime pool"""
302
- sandbox_type = SandboxType(sandbox_type)
303
- if sandbox_type != self.default_type:
304
- return self.create(sandbox_type=sandbox_type.value)
339
+ # If not specified, use the first one
340
+ sandbox_type = SandboxType(sandbox_type or self.default_type[0])
341
+
342
+ if sandbox_type not in self.pool_queues:
343
+ return self.create(sandbox_type=sandbox_type.value, meta=meta)
344
+
345
+ queue = self.pool_queues[sandbox_type]
305
346
 
306
347
  cnt = 0
307
348
  try:
@@ -313,17 +354,17 @@ class SandboxManager:
313
354
  cnt += 1
314
355
 
315
356
  # Add a new one to container
316
- container_name = self.create()
357
+ container_name = self.create(sandbox_type=sandbox_type)
317
358
  new_container_model = self.container_mapping.get(
318
359
  container_name,
319
360
  )
320
361
 
321
362
  if new_container_model:
322
- self.pool_queue.enqueue(
363
+ queue.enqueue(
323
364
  new_container_model,
324
365
  )
325
366
 
326
- container_json = self.pool_queue.dequeue()
367
+ container_json = queue.dequeue()
327
368
 
328
369
  if not container_json:
329
370
  raise RuntimeError(
@@ -331,6 +372,29 @@ class SandboxManager:
331
372
  )
332
373
 
333
374
  container_model = ContainerModel(**container_json)
375
+
376
+ # Add meta field to container
377
+ if meta and not container_model.meta:
378
+ container_model.meta = meta
379
+ self.container_mapping.set(
380
+ container_model.container_name,
381
+ container_model.model_dump(),
382
+ )
383
+ # Update session mapping
384
+ if "session_ctx_id" in meta:
385
+ env_ids = (
386
+ self.session_mapping.get(
387
+ meta["session_ctx_id"],
388
+ )
389
+ or []
390
+ )
391
+ if container_model.container_name not in env_ids:
392
+ env_ids.append(container_model.container_name)
393
+ self.session_mapping.set(
394
+ meta["session_ctx_id"],
395
+ env_ids,
396
+ )
397
+
334
398
  logger.debug(
335
399
  f"Retrieved container from pool:"
336
400
  f" {container_model.session_id}",
@@ -339,7 +403,7 @@ class SandboxManager:
339
403
  if (
340
404
  container_model.version
341
405
  != SandboxRegistry.get_image_by_type(
342
- self.default_type,
406
+ sandbox_type,
343
407
  )
344
408
  ):
345
409
  logger.warning(
@@ -370,37 +434,37 @@ class SandboxManager:
370
434
  self.release(container_model.session_id)
371
435
 
372
436
  except Exception as e:
373
- logger.error(
374
- f"Error getting container from pool, create a "
375
- f"new one. {e}: {traceback.format_exc()}",
437
+ logger.warning(
438
+ "Error getting container from pool, create a new one.",
376
439
  )
440
+ logger.debug(f"{e}: {traceback.format_exc()}")
377
441
  return self.create()
378
442
 
379
443
  @remote_wrapper()
380
444
  def create(
381
445
  self,
382
446
  sandbox_type=None,
383
- mount_dir=None,
447
+ mount_dir=None, # TODO: remove to avoid leaking
384
448
  storage_path=None,
385
449
  environment: Optional[Dict] = None,
450
+ meta: Optional[Dict] = None,
386
451
  ):
387
452
  if sandbox_type is not None:
388
453
  target_sandbox_type = SandboxType(sandbox_type)
389
454
  else:
390
- target_sandbox_type = self.default_type
455
+ target_sandbox_type = self.default_type[0]
456
+
457
+ config = SandboxRegistry.get_config_by_type(target_sandbox_type)
391
458
 
392
- image = SandboxRegistry.get_image_by_type(target_sandbox_type)
393
- if not image:
459
+ if not config:
394
460
  logger.warning(
395
- f"No image found for sandbox {target_sandbox_type}, "
396
- f"using default",
461
+ f"Not found sandbox {target_sandbox_type}, " f"using default",
397
462
  )
398
- image = SandboxRegistry.get_image_by_type(
399
- self.default_type,
463
+ config = SandboxRegistry.get_config_by_type(
464
+ self.default_type[0],
400
465
  )
466
+ image = config.image_name
401
467
 
402
- # TODO: enable for timeout for the sandbox (auto cleanup)
403
- config = SandboxRegistry.get_config_by_type(target_sandbox_type)
404
468
  environment = {
405
469
  **(config.environment if config.environment else {}),
406
470
  **(environment if environment else {}),
@@ -422,7 +486,7 @@ class SandboxManager:
422
486
  mount_dir = os.path.join(self.default_mount_dir, session_id)
423
487
  os.makedirs(mount_dir, exist_ok=True)
424
488
 
425
- if mount_dir:
489
+ if mount_dir and self.container_deployment != "agentrun":
426
490
  if not os.path.isabs(mount_dir):
427
491
  mount_dir = os.path.abspath(mount_dir)
428
492
 
@@ -433,12 +497,16 @@ class SandboxManager:
433
497
  session_id,
434
498
  )
435
499
 
436
- if mount_dir and storage_path:
500
+ if (
501
+ mount_dir
502
+ and storage_path
503
+ and self.container_deployment != "agentrun"
504
+ ):
437
505
  self.storage.download_folder(storage_path, mount_dir)
438
506
 
507
+ # Check for an existing container with the same name
508
+ container_name = self._generate_container_key(session_id)
439
509
  try:
440
- # Check for an existing container with the same name
441
- container_name = self._generate_container_key(session_id)
442
510
  if self.client.inspect(container_name):
443
511
  raise ValueError(
444
512
  f"Container with name {container_name} already exists.",
@@ -448,7 +516,7 @@ class SandboxManager:
448
516
  runtime_token = secrets.token_hex(16)
449
517
 
450
518
  # Prepare volume bindings if a mount directory is provided
451
- if mount_dir:
519
+ if mount_dir and self.container_deployment != "agentrun":
452
520
  volume_bindings = {
453
521
  mount_dir: {
454
522
  "bind": self.workdir,
@@ -458,7 +526,16 @@ class SandboxManager:
458
526
  else:
459
527
  volume_bindings = {}
460
528
 
461
- _id, ports, ip = self.client.create(
529
+ if self.readonly_mounts:
530
+ for host_path, container_path in self.readonly_mounts.items():
531
+ if not os.path.isabs(host_path):
532
+ host_path = os.path.abspath(host_path)
533
+ volume_bindings[host_path] = {
534
+ "bind": container_path,
535
+ "mode": "ro",
536
+ }
537
+
538
+ _id, ports, ip, *rest = self.client.create(
462
539
  image,
463
540
  name=container_name,
464
541
  ports=["80/tcp"], # Nginx
@@ -470,6 +547,10 @@ class SandboxManager:
470
547
  runtime_config=config.runtime_config,
471
548
  )
472
549
 
550
+ http_protocol = "http"
551
+ if rest and rest[0] == "https":
552
+ http_protocol = "https"
553
+
473
554
  if _id is None:
474
555
  return None
475
556
 
@@ -487,37 +568,44 @@ class SandboxManager:
487
568
  session_id=session_id,
488
569
  container_id=_id,
489
570
  container_name=container_name,
490
- base_url=f"http://{ip}:{ports[0]}/fastapi",
491
- browser_url=f"http://{ip}:{ports[0]}/steel-api"
492
- f"/{runtime_token}",
493
- front_browser_ws=f"ws://{ip}:"
494
- f"{ports[0]}/steel-api/"
495
- f"{runtime_token}/v1/sessions/cast",
496
- client_browser_ws=f"ws://{ip}:"
497
- f"{ports[0]}/steel-api/{runtime_token}/&sessionId"
498
- f"={BROWSER_SESSION_ID}",
499
- artifacts_sio=f"http://{ip}:{ports[0]}/v1",
571
+ url=f"{http_protocol}://{ip}:{ports[0]}",
500
572
  ports=[ports[0]],
501
573
  mount_dir=str(mount_dir),
502
574
  storage_path=storage_path,
503
575
  runtime_token=runtime_token,
504
576
  version=image,
577
+ meta=meta or {},
578
+ timeout=config.timeout,
505
579
  )
580
+
506
581
  # Register in mapping
507
582
  self.container_mapping.set(
508
583
  container_model.container_name,
509
584
  container_model.model_dump(),
510
585
  )
511
586
 
587
+ # Build mapping session_ctx_id to container_name
588
+ if meta and "session_ctx_id" in meta:
589
+ env_ids = (
590
+ self.session_mapping.get(
591
+ meta["session_ctx_id"],
592
+ )
593
+ or []
594
+ )
595
+ env_ids.append(container_model.container_name)
596
+ self.session_mapping.set(meta["session_ctx_id"], env_ids)
597
+
512
598
  logger.debug(
513
599
  f"Created container {container_name}"
514
600
  f":{container_model.model_dump()}",
515
601
  )
516
602
  return container_name
517
603
  except Exception as e:
518
- logger.error(
519
- f"Failed to create container: {e}: {traceback.format_exc()}",
604
+ logger.warning(
605
+ f"Failed to create container: {e}",
520
606
  )
607
+ logger.debug(f"{traceback.format_exc()}")
608
+ self.release(identity=container_name)
521
609
  return None
522
610
 
523
611
  @remote_wrapper()
@@ -536,6 +624,20 @@ class SandboxManager:
536
624
  # remove key in mapping before we remove container
537
625
  self.container_mapping.delete(container_json.get("container_name"))
538
626
 
627
+ # remove key in mapping
628
+ session_ctx_id = container_info.meta.get("session_ctx_id")
629
+ if session_ctx_id:
630
+ env_ids = self.session_mapping.get(session_ctx_id) or []
631
+ env_ids = [
632
+ eid
633
+ for eid in env_ids
634
+ if eid != container_info.container_name
635
+ ]
636
+ if env_ids:
637
+ self.session_mapping.set(session_ctx_id, env_ids)
638
+ else:
639
+ self.session_mapping.delete(session_ctx_id)
640
+
539
641
  self.client.stop(container_info.container_id, timeout=1)
540
642
  self.client.remove(container_info.container_id, force=True)
541
643
 
@@ -550,10 +652,10 @@ class SandboxManager:
550
652
 
551
653
  return True
552
654
  except Exception as e:
553
- logger.error(
554
- f"Failed to destroy container: {e}: "
555
- f"{traceback.format_exc()}",
655
+ logger.warning(
656
+ f"Failed to destroy container: {e}",
556
657
  )
658
+ logger.debug(f"{traceback.format_exc()}")
557
659
  return False
558
660
 
559
661
  @remote_wrapper()
@@ -632,7 +734,7 @@ class SandboxManager:
632
734
  self._generate_container_key(identity),
633
735
  )
634
736
  if container_model is None:
635
- return None
737
+ raise RuntimeError(f"No container found with id: {identity}.")
636
738
  if hasattr(container_model, "model_dump_json"):
637
739
  container_model = container_model.model_dump_json()
638
740
 
@@ -640,35 +742,26 @@ class SandboxManager:
640
742
 
641
743
  def _establish_connection(self, identity):
642
744
  container_model = ContainerModel(**self.get_info(identity))
643
- # TODO: make this more robust
644
- enable_browser = "browser" in container_model.version
645
745
 
646
746
  # TODO: remake docker name
647
747
  if (
648
748
  "sandbox-appworld" in container_model.version
649
749
  or "sandbox-bfcl" in container_model.version
650
750
  ):
651
- parsed = urlparse(container_model.base_url)
652
- base_url = urlunparse(
653
- (
654
- parsed.scheme,
655
- parsed.netloc,
656
- "",
657
- "",
658
- "",
659
- "",
660
- ),
661
- )
662
-
663
751
  return TrainingSandboxClient(
664
- base_url=base_url,
752
+ base_url=container_model.url,
665
753
  ).__enter__()
666
754
 
667
755
  return SandboxHttpClient(
668
756
  container_model,
669
- enable_browser=enable_browser,
670
757
  ).__enter__()
671
758
 
759
+ @remote_wrapper()
760
+ def check_health(self, identity):
761
+ """List tool"""
762
+ client = self._establish_connection(identity)
763
+ return client.check_health()
764
+
672
765
  @remote_wrapper()
673
766
  def list_tools(self, identity, tool_type=None, **kwargs):
674
767
  """List tool"""
@@ -691,3 +784,16 @@ class SandboxManager:
691
784
  server_configs=server_configs,
692
785
  overwrite=overwrite,
693
786
  )
787
+
788
+ @remote_wrapper()
789
+ def get_session_mapping(self, session_ctx_id: str) -> list:
790
+ """Get all container names bound to a session context"""
791
+ return self.session_mapping.get(session_ctx_id) or []
792
+
793
+ @remote_wrapper()
794
+ def list_session_keys(self) -> list:
795
+ """Return all session_ctx_id keys currently in mapping"""
796
+ session_keys = []
797
+ for key in self.session_mapping.scan():
798
+ session_keys.append(key)
799
+ return session_keys