agentscope-runtime 0.1.1__py3-none-any.whl → 0.1.3__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 (33) hide show
  1. agentscope_runtime/engine/agents/agentscope_agent/agent.py +105 -50
  2. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +16 -3
  3. agentscope_runtime/engine/helpers/helper.py +33 -0
  4. agentscope_runtime/engine/runner.py +33 -1
  5. agentscope_runtime/engine/schemas/agent_schemas.py +208 -13
  6. agentscope_runtime/engine/services/context_manager.py +34 -1
  7. agentscope_runtime/engine/services/rag_service.py +195 -0
  8. agentscope_runtime/engine/services/reme_personal_memory_service.py +106 -0
  9. agentscope_runtime/engine/services/reme_task_memory_service.py +11 -0
  10. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +25 -0
  11. agentscope_runtime/sandbox/box/sandbox.py +60 -7
  12. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -2
  13. agentscope_runtime/sandbox/box/training_box/env_service.py +1 -1
  14. agentscope_runtime/sandbox/box/training_box/environments/bfcl/bfcl_dataprocess.py +216 -0
  15. agentscope_runtime/sandbox/box/training_box/environments/bfcl/bfcl_env.py +380 -0
  16. agentscope_runtime/sandbox/box/training_box/environments/bfcl/env_handler.py +934 -0
  17. agentscope_runtime/sandbox/box/training_box/training_box.py +139 -9
  18. agentscope_runtime/sandbox/client/http_client.py +1 -1
  19. agentscope_runtime/sandbox/enums.py +2 -0
  20. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +19 -9
  21. agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +61 -6
  22. agentscope_runtime/sandbox/manager/sandbox_manager.py +95 -35
  23. agentscope_runtime/sandbox/manager/server/app.py +128 -17
  24. agentscope_runtime/sandbox/model/__init__.py +1 -5
  25. agentscope_runtime/sandbox/model/manager_config.py +2 -13
  26. agentscope_runtime/sandbox/tools/mcp_tool.py +1 -1
  27. agentscope_runtime/version.py +1 -1
  28. {agentscope_runtime-0.1.1.dist-info → agentscope_runtime-0.1.3.dist-info}/METADATA +59 -3
  29. {agentscope_runtime-0.1.1.dist-info → agentscope_runtime-0.1.3.dist-info}/RECORD +33 -27
  30. {agentscope_runtime-0.1.1.dist-info → agentscope_runtime-0.1.3.dist-info}/WHEEL +0 -0
  31. {agentscope_runtime-0.1.1.dist-info → agentscope_runtime-0.1.3.dist-info}/entry_points.txt +0 -0
  32. {agentscope_runtime-0.1.1.dist-info → agentscope_runtime-0.1.3.dist-info}/licenses/LICENSE +0 -0
  33. {agentscope_runtime-0.1.1.dist-info → agentscope_runtime-0.1.3.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ with specific configuration and tool calling methods.
7
7
  """
8
8
  import platform
9
9
  from typing import Dict, Optional
10
+ import os
10
11
 
11
12
  from ...registry import SandboxRegistry
12
13
  from ...enums import SandboxType
@@ -21,14 +22,6 @@ def get_image_tag() -> str:
21
22
  return IMAGE_TAG
22
23
 
23
24
 
24
- @SandboxRegistry.register(
25
- f"agentscope/runtime-sandbox-appworld:{get_image_tag()}",
26
- sandbox_type=SandboxType.APPWORLD,
27
- runtime_config={"shm_size": "5.06gb"},
28
- security_level="medium",
29
- timeout=30,
30
- description="appworld Sandbox",
31
- )
32
25
  class TrainingSandbox(Sandbox):
33
26
  """
34
27
  Training Sandbox class for managing and executing training-related tasks.
@@ -43,6 +36,7 @@ class TrainingSandbox(Sandbox):
43
36
  timeout: int = 3000,
44
37
  base_url: Optional[str] = None,
45
38
  bearer_token: Optional[str] = None,
39
+ box_type: SandboxType = SandboxType.APPWORLD,
46
40
  ):
47
41
  """
48
42
  Initialize the Training Sandbox.
@@ -58,7 +52,7 @@ class TrainingSandbox(Sandbox):
58
52
  timeout,
59
53
  base_url,
60
54
  bearer_token,
61
- SandboxType.APPWORLD,
55
+ box_type,
62
56
  )
63
57
 
64
58
  def create_instance(
@@ -217,3 +211,139 @@ class TrainingSandbox(Sandbox):
217
211
  "instance_id": instance_id,
218
212
  },
219
213
  )
214
+
215
+
216
+ @SandboxRegistry.register(
217
+ f"agentscope/runtime-sandbox-appworld:{get_image_tag()}",
218
+ sandbox_type=SandboxType.APPWORLD,
219
+ runtime_config={"shm_size": "5.06gb"},
220
+ security_level="medium",
221
+ timeout=30,
222
+ description="appworld Sandbox",
223
+ )
224
+ class APPWorldSandbox(TrainingSandbox):
225
+ """
226
+ Training Sandbox class for managing and executing training-related tasks.
227
+
228
+ This class provides methods to create, manage, and interact with
229
+ training environment instances using specialized tool calls.
230
+ """
231
+
232
+ def __init__(
233
+ self,
234
+ sandbox_id: Optional[str] = None,
235
+ timeout: int = 3000,
236
+ base_url: Optional[str] = None,
237
+ bearer_token: Optional[str] = None,
238
+ ):
239
+ """
240
+ Initialize the Training Sandbox.
241
+
242
+ Args:
243
+ sandbox_id (Optional[str]): Unique identifier for the sandbox.
244
+ timeout (int): Maximum time allowed for sandbox operations.
245
+ base_url (Optional[str]): Base URL for sandbox API.
246
+ bearer_token (Optional[str]): Authentication token for API access.
247
+ """
248
+ super().__init__(
249
+ sandbox_id,
250
+ timeout,
251
+ base_url,
252
+ bearer_token,
253
+ SandboxType.APPWORLD,
254
+ )
255
+
256
+
257
+ DATASET_SUB_TYPE = os.environ.get("DATASET_SUB_TYPE", "multi_turn")
258
+
259
+
260
+ @SandboxRegistry.register(
261
+ f"agentscope/runtime-sandbox-bfcl:{get_image_tag()}",
262
+ sandbox_type=SandboxType.BFCL,
263
+ runtime_config={"shm_size": "8.06gb"},
264
+ security_level="medium",
265
+ environment={
266
+ "OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", ""),
267
+ "BFCL_DATA_PATH": f"/agentscope_runtime/training_box/bfcl/multi_turn/"
268
+ f"{DATASET_SUB_TYPE}_processed.jsonl",
269
+ "BFCL_SPLID_ID_PATH": f"/agentscope_runtime/training_box/"
270
+ f"bfcl/multi_turn/"
271
+ f"{DATASET_SUB_TYPE}_split_ids.json",
272
+ },
273
+ # ["all","all_scoring","multi_turn","single_turn",
274
+ # "live","non_live","non_python","python"]
275
+ timeout=30,
276
+ description="bfcl Sandbox",
277
+ )
278
+ class BFCLSandbox(TrainingSandbox):
279
+ """
280
+ Training Sandbox class for managing and executing training-related tasks.
281
+
282
+ This class provides methods to create, manage, and interact with
283
+ training environment instances using specialized tool calls.
284
+ """
285
+
286
+ def __init__(
287
+ self,
288
+ sandbox_id: Optional[str] = None,
289
+ timeout: int = 3000,
290
+ base_url: Optional[str] = None,
291
+ bearer_token: Optional[str] = None,
292
+ ):
293
+ """
294
+ Initialize the Training Sandbox.
295
+
296
+ Args:
297
+ sandbox_id (Optional[str]): Unique identifier for the sandbox.
298
+ timeout (int): Maximum time allowed for sandbox operations.
299
+ base_url (Optional[str]): Base URL for sandbox API.
300
+ bearer_token (Optional[str]): Authentication token for API access.
301
+ """
302
+ super().__init__(
303
+ sandbox_id,
304
+ timeout,
305
+ base_url,
306
+ bearer_token,
307
+ SandboxType.BFCL,
308
+ )
309
+
310
+
311
+ @SandboxRegistry.register(
312
+ f"agentscope/runtime-sandbox-webshop:{get_image_tag()}",
313
+ sandbox_type=SandboxType.WEBSHOP,
314
+ runtime_config={"shm_size": "5.06gb"},
315
+ security_level="medium",
316
+ timeout=30,
317
+ description="webshop Sandbox",
318
+ )
319
+ class WebShopSandbox(TrainingSandbox):
320
+ """
321
+ Training Sandbox class for managing and executing training-related tasks.
322
+
323
+ This class provides methods to create, manage, and interact with
324
+ training environment instances using specialized tool calls.
325
+ """
326
+
327
+ def __init__(
328
+ self,
329
+ sandbox_id: Optional[str] = None,
330
+ timeout: int = 3000,
331
+ base_url: Optional[str] = None,
332
+ bearer_token: Optional[str] = None,
333
+ ):
334
+ """
335
+ Initialize the Training Sandbox.
336
+
337
+ Args:
338
+ sandbox_id (Optional[str]): Unique identifier for the sandbox.
339
+ timeout (int): Maximum time allowed for sandbox operations.
340
+ base_url (Optional[str]): Base URL for sandbox API.
341
+ bearer_token (Optional[str]): Authentication token for API access.
342
+ """
343
+ super().__init__(
344
+ sandbox_id,
345
+ timeout,
346
+ base_url,
347
+ bearer_token,
348
+ SandboxType.BFCL,
349
+ )
@@ -195,7 +195,7 @@ class SandboxHttpClient:
195
195
  mcp_tools = response.json()
196
196
  mcp_tools["generic"] = self.generic_tools
197
197
  if tool_type:
198
- return {tool_type: mcp_tools.get(tool_type, [])}
198
+ return {tool_type: mcp_tools.get(tool_type, {})}
199
199
  return mcp_tools
200
200
  except requests.exceptions.RequestException as e:
201
201
  logging.error(f"An error occurred: {e}")
@@ -66,3 +66,5 @@ class SandboxType(DynamicEnum):
66
66
  BROWSER = "browser"
67
67
  FILESYSTEM = "filesystem"
68
68
  APPWORLD = "appworld"
69
+ BFCL = "bfcl"
70
+ WEBSHOP = "webshop"
@@ -217,9 +217,12 @@ class DockerClient(BaseClient):
217
217
  acr_registry = "agentscope-registry.ap-southeast-1.cr.aliyuncs.com"
218
218
  acr_image = f"{acr_registry}/{image}"
219
219
 
220
- logger.debug(f"Attempting to pull from ACR: {acr_image}")
220
+ logger.info(
221
+ f"Attempting to pull from ACR: {acr_image}, it might take "
222
+ f"several minutes.",
223
+ )
221
224
  self.client.images.pull(acr_image)
222
- logger.debug(f"Successfully pulled image from ACR: {acr_image}")
225
+ logger.info(f"Successfully pulled image from ACR: {acr_image}")
223
226
 
224
227
  # Retag the image
225
228
  acr_img_obj = self.client.images.get(acr_image)
@@ -234,7 +237,9 @@ class DockerClient(BaseClient):
234
237
  logger.debug(f"Failed to remove original tag: {e}")
235
238
  return True
236
239
  except Exception as e:
237
- logger.error(f"Failed to pull from ACR: {e}")
240
+ logger.error(
241
+ f"Failed to pull from ACR: {e}, {traceback.format_exc()}",
242
+ )
238
243
  return False
239
244
 
240
245
  def create(
@@ -263,11 +268,15 @@ class DockerClient(BaseClient):
263
268
  self.client.images.get(image)
264
269
  logger.debug(f"Image '{image}' found locally.")
265
270
  except docker.errors.ImageNotFound:
266
- logger.debug(
271
+ logger.info(
267
272
  f"Image '{image}' not found locally. "
268
273
  f"Attempting to pull it...",
269
274
  )
270
275
  try:
276
+ logger.info(
277
+ f"Attempting to pull: {image}, "
278
+ f"it might take several minutes.",
279
+ )
271
280
  self.client.images.pull(image)
272
281
  logger.debug(
273
282
  f"Image '{image}' successfully pulled from default "
@@ -278,7 +287,8 @@ class DockerClient(BaseClient):
278
287
  logger.warning(
279
288
  f"Failed to pull from default registry: {e}",
280
289
  )
281
- logger.debug("Trying to pull from ACR fallback...")
290
+ logger.warning("Trying to pull from ACR fallback...")
291
+
282
292
  pull_success = self._try_pull_from_acr(image)
283
293
 
284
294
  if not pull_success:
@@ -286,11 +296,11 @@ class DockerClient(BaseClient):
286
296
  f"Failed to pull image '{image}' from both "
287
297
  f"default and ACR",
288
298
  )
289
- return False
299
+ return None, None, None
290
300
 
291
301
  except docker.errors.APIError as e:
292
302
  logger.error(f"Error occurred while checking the image: {e}")
293
- return False
303
+ return None, None, None
294
304
 
295
305
  # Create and run the container
296
306
  container = self.client.containers.run(
@@ -304,10 +314,10 @@ class DockerClient(BaseClient):
304
314
  )
305
315
  container.reload()
306
316
  _id = container.id
307
- return _id, list(port_mapping.values())
317
+ return _id, list(port_mapping.values()), "localhost"
308
318
  except Exception as e:
309
319
  logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
310
- return None, None
320
+ return None, None, None
311
321
 
312
322
  def start(self, container_id):
313
323
  """Start a Docker container."""
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # pylint: disable=too-many-branches
3
+ import os
3
4
  import time
4
5
  import hashlib
5
6
  import traceback
@@ -89,7 +90,9 @@ class KubernetesClient(BaseClient):
89
90
  runtime_config = {}
90
91
 
91
92
  container_name = name or "main-container"
93
+
92
94
  # Container specification
95
+ # TODO: use image from docker registry first
93
96
  container = client.V1Container(
94
97
  name=container_name,
95
98
  image=f"agentscope-registry.ap-southeast-1.cr.aliyuncs.com"
@@ -244,6 +247,7 @@ class KubernetesClient(BaseClient):
244
247
  )
245
248
 
246
249
  exposed_ports = []
250
+ pod_node_ip = "localhost"
247
251
  # Auto-create services for exposed ports (like Docker's port
248
252
  # mapping)
249
253
  if ports:
@@ -259,19 +263,22 @@ class KubernetesClient(BaseClient):
259
263
  parsed_ports,
260
264
  )
261
265
  if service_created:
262
- exposed_ports = self._get_service_node_ports(name)
266
+ (
267
+ exposed_ports,
268
+ pod_node_ip,
269
+ ) = self._get_service_node_ports(name)
263
270
  logger.debug(
264
271
  f"Pod '{name}' created with exposed ports: {exposed_ports}",
265
272
  )
266
273
 
267
274
  if not self.wait_for_pod_ready(name, timeout=60):
268
275
  logger.error(f"Pod '{name}' failed to become ready")
269
- return None, None
276
+ return None, None, None
270
277
 
271
- return name, exposed_ports
278
+ return name, exposed_ports, pod_node_ip
272
279
  except Exception as e:
273
280
  logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
274
- return None, None
281
+ return None, None, None
275
282
 
276
283
  def start(self, container_id):
277
284
  """
@@ -510,7 +517,10 @@ class KubernetesClient(BaseClient):
510
517
  service = client.V1Service(
511
518
  api_version="v1",
512
519
  kind="Service",
513
- metadata=client.V1ObjectMeta(name=service_name),
520
+ metadata=client.V1ObjectMeta(
521
+ name=service_name,
522
+ namespace=self.namespace,
523
+ ),
514
524
  spec=service_spec,
515
525
  )
516
526
 
@@ -540,11 +550,56 @@ class KubernetesClient(BaseClient):
540
550
  )
541
551
 
542
552
  node_ports = []
553
+ pod_node_ip = self._get_pod_node_ip(pod_name)
554
+
543
555
  for port in service_info.spec.ports:
544
556
  if port.node_port:
545
557
  node_ports.append(port.node_port)
546
558
 
547
- return node_ports
559
+ return node_ports, pod_node_ip
548
560
  except Exception as e:
549
561
  logger.error(f"Failed to get node port: {e}")
550
562
  return None
563
+
564
+ def _get_pod_node_ip(self, pod_name):
565
+ """Get the IP of the node where the pod is running"""
566
+
567
+ # Check if we are running in Colima, where pod runs in VM
568
+ docker_host = os.getenv("DOCKER_HOST", "")
569
+ if "colima" in docker_host.lower():
570
+ return "localhost"
571
+
572
+ try:
573
+ pod = self.v1.read_namespaced_pod(
574
+ name=pod_name,
575
+ namespace=self.namespace,
576
+ )
577
+
578
+ node_name = pod.spec.node_name
579
+ if not node_name:
580
+ logger.warning(
581
+ f"Pod {pod_name} is not scheduled to any node yet",
582
+ )
583
+ return None
584
+
585
+ node = self.v1.read_node(name=node_name)
586
+
587
+ external_ip = None
588
+ internal_ip = None
589
+
590
+ for address in node.status.addresses:
591
+ if address.type == "ExternalIP":
592
+ external_ip = address.address
593
+ elif address.type == "InternalIP":
594
+ internal_ip = address.address
595
+
596
+ result_ip = external_ip or internal_ip
597
+ logger.debug(
598
+ f"Using IP: {result_ip} (external: {external_ip}, internal:"
599
+ f" {internal_ip})",
600
+ )
601
+ return result_ip
602
+
603
+ except Exception as e:
604
+ logger.error(f"Failed to get pod node IP: {e}")
605
+ return None
@@ -2,12 +2,14 @@
2
2
  # pylint: disable=redefined-outer-name, protected-access, too-many-branches
3
3
  import logging
4
4
  import os
5
+ import json
5
6
  import secrets
6
7
  import inspect
7
8
  import traceback
8
9
 
9
10
  from functools import wraps
10
11
  from typing import Optional, Dict
12
+ from urllib.parse import urlparse, urlunparse
11
13
 
12
14
  import shortuuid
13
15
  import requests
@@ -15,7 +17,6 @@ import requests
15
17
  from ..model import (
16
18
  ContainerModel,
17
19
  SandboxManagerEnvConfig,
18
- DEFAULT_LOCAL_MANAGER_CONFIG,
19
20
  )
20
21
  from ..enums import SandboxType
21
22
  from ..registry import SandboxRegistry
@@ -81,7 +82,7 @@ def remote_wrapper(
81
82
  class SandboxManager:
82
83
  def __init__(
83
84
  self,
84
- config: SandboxManagerEnvConfig = DEFAULT_LOCAL_MANAGER_CONFIG,
85
+ config: Optional[SandboxManagerEnvConfig] = None,
85
86
  base_url=None,
86
87
  bearer_token=None,
87
88
  default_type: SandboxType | str = SandboxType.BASE,
@@ -96,19 +97,28 @@ class SandboxManager:
96
97
  self.http_session.headers.update(
97
98
  {"Authorization": f"Bearer {bearer_token}"},
98
99
  )
100
+ # Remote mode, return directly
101
+ return
99
102
  else:
100
103
  self.http_session = None
101
104
  self.base_url = None
102
105
 
106
+ if not config:
107
+ config = SandboxManagerEnvConfig(
108
+ file_system="local",
109
+ redis_enabled=False,
110
+ container_deployment="docker",
111
+ pool_size=0,
112
+ default_mount_dir="sessions_mount_dir",
113
+ )
114
+
103
115
  self.default_type = SandboxType(default_type)
104
116
  self.workdir = "/workspace"
105
117
 
106
118
  self.config = config
107
119
  self.pool_size = self.config.pool_size
108
120
  self.prefix = self.config.container_prefix_key
109
- self.default_mount_dir = (
110
- self.config.default_mount_dir or "sessions_mount_dir"
111
- )
121
+ self.default_mount_dir = self.config.default_mount_dir
112
122
  self.storage_folder = (
113
123
  self.config.storage_folder or self.default_mount_dir
114
124
  )
@@ -197,8 +207,35 @@ class SandboxManager:
197
207
  try:
198
208
  response.raise_for_status()
199
209
  except requests.exceptions.HTTPError as e:
200
- logger.error(f"Error making request: {e}")
201
- return {"data": f"Error: {e}"}
210
+ error_components = [
211
+ f"HTTP {response.status_code} Error: {str(e)}",
212
+ ]
213
+
214
+ try:
215
+ server_response = response.json()
216
+ if "detail" in server_response:
217
+ error_components.append(
218
+ f"Server Detail: {server_response['detail']}",
219
+ )
220
+ elif "error" in server_response:
221
+ error_components.append(
222
+ f"Server Error: {server_response['error']}",
223
+ )
224
+ else:
225
+ error_components.append(
226
+ f"Server Response: {server_response}",
227
+ )
228
+ except (ValueError, json.JSONDecodeError):
229
+ if response.text:
230
+ error_components.append(
231
+ f"Server Response: {response.text}",
232
+ )
233
+
234
+ error = " | ".join(error_components)
235
+
236
+ logger.error(f"Error making request: {error}")
237
+
238
+ return {"data": f"Error: {error}"}
202
239
 
203
240
  return response.json()
204
241
 
@@ -380,20 +417,24 @@ class SandboxManager:
380
417
  short_uuid = shortuuid.ShortUUID(alphabet=alphabet).uuid()
381
418
  session_id = str(short_uuid)
382
419
 
383
- if mount_dir is None:
384
- mount_dir = os.path.join(self.default_mount_dir, session_id)
385
- os.makedirs(mount_dir, exist_ok=True)
420
+ if not mount_dir:
421
+ if self.default_mount_dir:
422
+ mount_dir = os.path.join(self.default_mount_dir, session_id)
423
+ os.makedirs(mount_dir, exist_ok=True)
386
424
 
387
- if not os.path.isabs(mount_dir):
388
- mount_dir = os.path.abspath(mount_dir)
425
+ if mount_dir:
426
+ if not os.path.isabs(mount_dir):
427
+ mount_dir = os.path.abspath(mount_dir)
389
428
 
390
429
  if storage_path is None:
391
- storage_path = self.storage.path_join(
392
- self.storage_folder,
393
- session_id,
394
- )
430
+ if self.storage_folder:
431
+ storage_path = self.storage.path_join(
432
+ self.storage_folder,
433
+ session_id,
434
+ )
395
435
 
396
- self.storage.download_folder(storage_path, mount_dir)
436
+ if mount_dir and storage_path:
437
+ self.storage.download_folder(storage_path, mount_dir)
397
438
 
398
439
  try:
399
440
  # Check for an existing container with the same name
@@ -407,14 +448,17 @@ class SandboxManager:
407
448
  runtime_token = secrets.token_hex(16)
408
449
 
409
450
  # Prepare volume bindings if a mount directory is provided
410
- volume_bindings = {
411
- mount_dir: {
412
- "bind": self.workdir,
413
- "mode": "rw",
414
- },
415
- }
451
+ if mount_dir:
452
+ volume_bindings = {
453
+ mount_dir: {
454
+ "bind": self.workdir,
455
+ "mode": "rw",
456
+ },
457
+ }
458
+ else:
459
+ volume_bindings = {}
416
460
 
417
- _id, ports = self.client.create(
461
+ _id, ports, ip = self.client.create(
418
462
  image,
419
463
  name=container_name,
420
464
  ports=["80/tcp"], # Nginx
@@ -443,16 +487,16 @@ class SandboxManager:
443
487
  session_id=session_id,
444
488
  container_id=_id,
445
489
  container_name=container_name,
446
- base_url=f"http://localhost:{ports[0]}/fastapi",
447
- browser_url=f"http://localhost:{ports[0]}/steel-api"
490
+ base_url=f"http://{ip}:{ports[0]}/fastapi",
491
+ browser_url=f"http://{ip}:{ports[0]}/steel-api"
448
492
  f"/{runtime_token}",
449
- front_browser_ws=f"ws://localhost:"
493
+ front_browser_ws=f"ws://{ip}:"
450
494
  f"{ports[0]}/steel-api/"
451
495
  f"{runtime_token}/v1/sessions/cast",
452
- client_browser_ws=f"ws://localhost:"
496
+ client_browser_ws=f"ws://{ip}:"
453
497
  f"{ports[0]}/steel-api/{runtime_token}/&sessionId"
454
498
  f"={BROWSER_SESSION_ID}",
455
- artifacts_sio=f"http://localhost:{ports[0]}/v1",
499
+ artifacts_sio=f"http://{ip}:{ports[0]}/v1",
456
500
  ports=[ports[0]],
457
501
  mount_dir=str(mount_dir),
458
502
  storage_path=storage_path,
@@ -498,10 +542,11 @@ class SandboxManager:
498
542
  logger.debug(f"Container for {identity} destroyed.")
499
543
 
500
544
  # Upload to storage
501
- self.storage.upload_folder(
502
- container_info.mount_dir,
503
- container_info.storage_path,
504
- )
545
+ if container_info.mount_dir and container_info.storage_path:
546
+ self.storage.upload_folder(
547
+ container_info.mount_dir,
548
+ container_info.storage_path,
549
+ )
505
550
 
506
551
  return True
507
552
  except Exception as e:
@@ -599,9 +644,24 @@ class SandboxManager:
599
644
  enable_browser = "browser" in container_model.version
600
645
 
601
646
  # TODO: remake docker name
602
- if "appworld" in container_model.version:
647
+ if (
648
+ "sandbox-appworld" in container_model.version
649
+ or "sandbox-bfcl" in container_model.version
650
+ ):
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
+
603
663
  return TrainingSandboxClient(
604
- base_url=f"http://localhost:{container_model.ports[0]}",
664
+ base_url=base_url,
605
665
  ).__enter__()
606
666
 
607
667
  return SandboxHttpClient(