agentscope-runtime 1.0.2__py3-none-any.whl → 1.0.4__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 (58) hide show
  1. agentscope_runtime/adapters/agentscope/stream.py +2 -9
  2. agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
  3. agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
  4. agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
  5. agentscope_runtime/adapters/utils.py +6 -0
  6. agentscope_runtime/cli/commands/deploy.py +383 -0
  7. agentscope_runtime/common/collections/redis_mapping.py +4 -1
  8. agentscope_runtime/common/container_clients/knative_client.py +466 -0
  9. agentscope_runtime/engine/__init__.py +4 -0
  10. agentscope_runtime/engine/app/agent_app.py +48 -5
  11. agentscope_runtime/engine/constant.py +1 -0
  12. agentscope_runtime/engine/deployers/__init__.py +12 -0
  13. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +31 -1
  14. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +458 -41
  15. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +76 -0
  16. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +749 -0
  17. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  18. agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
  19. agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
  20. agentscope_runtime/engine/deployers/kubernetes_deployer.py +3 -0
  21. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +8 -2
  22. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +5 -0
  23. agentscope_runtime/engine/deployers/utils/net_utils.py +65 -0
  24. agentscope_runtime/engine/runner.py +17 -3
  25. agentscope_runtime/engine/schemas/exception.py +24 -0
  26. agentscope_runtime/engine/services/agent_state/redis_state_service.py +61 -8
  27. agentscope_runtime/engine/services/agent_state/state_service_factory.py +2 -5
  28. agentscope_runtime/engine/services/memory/redis_memory_service.py +129 -25
  29. agentscope_runtime/engine/services/session_history/redis_session_history_service.py +160 -34
  30. agentscope_runtime/engine/tracing/wrapper.py +18 -4
  31. agentscope_runtime/sandbox/__init__.py +14 -6
  32. agentscope_runtime/sandbox/box/base/__init__.py +2 -2
  33. agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
  34. agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
  35. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
  36. agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
  37. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
  38. agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
  39. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
  40. agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
  41. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
  42. agentscope_runtime/sandbox/box/sandbox.py +98 -65
  43. agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
  44. agentscope_runtime/sandbox/build.py +50 -57
  45. agentscope_runtime/sandbox/client/__init__.py +6 -1
  46. agentscope_runtime/sandbox/client/async_http_client.py +339 -0
  47. agentscope_runtime/sandbox/client/base.py +74 -0
  48. agentscope_runtime/sandbox/client/http_client.py +108 -329
  49. agentscope_runtime/sandbox/enums.py +7 -0
  50. agentscope_runtime/sandbox/manager/sandbox_manager.py +264 -4
  51. agentscope_runtime/sandbox/manager/server/app.py +7 -1
  52. agentscope_runtime/version.py +1 -1
  53. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/METADATA +109 -29
  54. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/RECORD +58 -46
  55. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/WHEEL +0 -0
  56. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/entry_points.txt +0 -0
  57. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/licenses/LICENSE +0 -0
  58. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,8 @@
2
2
  # pylint: disable=redefined-outer-name, protected-access
3
3
  # pylint: disable=too-many-branches, too-many-statements
4
4
  # pylint: disable=redefined-outer-name, protected-access, too-many-branches
5
+ # pylint: disable=too-many-public-methods
6
+ import asyncio
5
7
  import inspect
6
8
  import json
7
9
  import logging
@@ -13,8 +15,13 @@ from typing import Optional, Dict, Union, List
13
15
 
14
16
  import requests
15
17
  import shortuuid
18
+ import httpx
16
19
 
17
- from ..client import SandboxHttpClient, TrainingSandboxClient
20
+ from ..client import (
21
+ SandboxHttpClient,
22
+ TrainingSandboxClient,
23
+ SandboxHttpAsyncClient,
24
+ )
18
25
  from ..enums import SandboxType
19
26
  from ..manager.storage import (
20
27
  LocalStorage,
@@ -31,6 +38,7 @@ from ...common.collections import (
31
38
  InMemoryMapping,
32
39
  InMemoryQueue,
33
40
  )
41
+ from ..constant import TIMEOUT
34
42
 
35
43
  logging.basicConfig(level=logging.INFO)
36
44
  logger = logging.getLogger(__name__)
@@ -76,6 +84,51 @@ def remote_wrapper(
76
84
  return decorator
77
85
 
78
86
 
87
+ def remote_wrapper_async(
88
+ method: str = "POST",
89
+ success_key: str = "data",
90
+ ):
91
+ """
92
+ Async decorator to handle both remote and local method execution.
93
+ Supports awaitable functions.
94
+ """
95
+
96
+ def decorator(func):
97
+ @wraps(func)
98
+ async def wrapper(self, *args, **kwargs):
99
+ # Remote mode
100
+ if hasattr(self, "httpx_client") and self.httpx_client is not None:
101
+ endpoint = "/" + func.__name__
102
+
103
+ # Build JSON data from args/kwargs
104
+ sig = inspect.signature(func)
105
+ param_names = list(sig.parameters.keys())[1:] # Skip 'self'
106
+ data = dict(zip(param_names, args))
107
+ data.update(kwargs)
108
+
109
+ # Make async HTTP request
110
+ response = await self._make_request_async(
111
+ method,
112
+ endpoint,
113
+ data,
114
+ )
115
+
116
+ if success_key:
117
+ return response.get(success_key)
118
+ return response
119
+
120
+ # Local mode
121
+ return await func(self, *args, **kwargs)
122
+
123
+ wrapper._is_remote_wrapper = True
124
+ wrapper._http_method = method
125
+ wrapper._path = "/" + func.__name__
126
+
127
+ return wrapper
128
+
129
+ return decorator
130
+
131
+
79
132
  class SandboxManager:
80
133
  def __init__(
81
134
  self,
@@ -92,16 +145,23 @@ class SandboxManager:
92
145
  # Initialize HTTP session for remote mode with bearer token
93
146
  # authentication
94
147
  self.http_session = requests.Session()
95
- self.http_session.timeout = 30
148
+
149
+ # For async HTTP
150
+ self.httpx_client = httpx.AsyncClient(timeout=TIMEOUT)
151
+
96
152
  self.base_url = base_url.rstrip("/")
97
153
  if bearer_token:
98
154
  self.http_session.headers.update(
99
155
  {"Authorization": f"Bearer {bearer_token}"},
100
156
  )
157
+ self.httpx_client.headers.update(
158
+ {"Authorization": f"Bearer {bearer_token}"},
159
+ )
101
160
  # Remote mode, return directly
102
161
  return
103
162
  else:
104
163
  self.http_session = None
164
+ self.httpx_client = None
105
165
  self.base_url = None
106
166
 
107
167
  if not config:
@@ -226,6 +286,51 @@ class SandboxManager:
226
286
  )
227
287
  self.cleanup()
228
288
 
289
+ if self.http_session:
290
+ try:
291
+ self.http_session.close()
292
+ logger.debug("HTTP session closed.")
293
+ except Exception as e:
294
+ logger.warning(f"Error closing http_session: {e}")
295
+
296
+ if self.httpx_client:
297
+ try:
298
+ loop = asyncio.get_event_loop()
299
+ if loop.is_running():
300
+ asyncio.ensure_future(self.httpx_client.aclose())
301
+ else:
302
+ loop.run_until_complete(self.httpx_client.aclose())
303
+ logger.debug("HTTPX async client closed.")
304
+ except Exception as e:
305
+ logger.warning(f"Error closing httpx_client: {e}")
306
+
307
+ async def __aenter__(self):
308
+ logger.debug(
309
+ "Entering SandboxManager context (async). "
310
+ "Cleanup will be performed automatically on async exit.",
311
+ )
312
+ return self
313
+
314
+ async def __aexit__(self, exc_type, exc_value, tb):
315
+ logger.debug(
316
+ "Exiting SandboxManager context (async). Cleaning up resources.",
317
+ )
318
+ await self.cleanup_async()
319
+
320
+ if self.http_session:
321
+ try:
322
+ self.http_session.close()
323
+ logger.debug("HTTP session closed.")
324
+ except Exception as e:
325
+ logger.warning(f"Error closing http_session: {e}")
326
+
327
+ if self.httpx_client:
328
+ try:
329
+ await self.httpx_client.aclose()
330
+ logger.debug("HTTPX async client closed.")
331
+ except Exception as e:
332
+ logger.warning(f"Error closing httpx_client: {e}")
333
+
229
334
  def _generate_container_key(self, session_id):
230
335
  return f"{self.prefix}{session_id}"
231
336
 
@@ -235,9 +340,14 @@ class SandboxManager:
235
340
  """
236
341
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
237
342
  if method.upper() == "GET":
238
- response = self.http_session.get(url, params=data)
343
+ response = self.http_session.get(url, params=data, timeout=TIMEOUT)
239
344
  else:
240
- response = self.http_session.request(method, url, json=data)
345
+ response = self.http_session.request(
346
+ method,
347
+ url,
348
+ json=data,
349
+ timeout=TIMEOUT,
350
+ )
241
351
 
242
352
  try:
243
353
  response.raise_for_status()
@@ -274,6 +384,56 @@ class SandboxManager:
274
384
 
275
385
  return response.json()
276
386
 
387
+ async def _make_request_async(
388
+ self,
389
+ method: str,
390
+ endpoint: str,
391
+ data: dict,
392
+ ):
393
+ """
394
+ Make an asynchronous HTTP request to the specified endpoint.
395
+ """
396
+ url = f"{self.base_url}/{endpoint.lstrip('/')}"
397
+ if method.upper() == "GET":
398
+ response = await self.httpx_client.get(url, params=data)
399
+ else:
400
+ response = await self.httpx_client.request(method, url, json=data)
401
+
402
+ try:
403
+ response.raise_for_status()
404
+ except httpx.HTTPStatusError as e:
405
+ error_components = [
406
+ f"HTTP {response.status_code} Error: {str(e)}",
407
+ ]
408
+
409
+ try:
410
+ server_response = response.json()
411
+ if "detail" in server_response:
412
+ error_components.append(
413
+ f"Server Detail: {server_response['detail']}",
414
+ )
415
+ elif "error" in server_response:
416
+ error_components.append(
417
+ f"Server Error: {server_response['error']}",
418
+ )
419
+ else:
420
+ error_components.append(
421
+ f"Server Response: {server_response}",
422
+ )
423
+ except (ValueError, json.JSONDecodeError):
424
+ if response.text:
425
+ error_components.append(
426
+ f"Server Response: {response.text}",
427
+ )
428
+
429
+ error = " | ".join(error_components)
430
+
431
+ logger.error(f"Error making request: {error}")
432
+
433
+ return {"data": f"Error: {error}"}
434
+
435
+ return response.json()
436
+
277
437
  def _init_container_pool(self):
278
438
  """
279
439
  Init runtime pool
@@ -337,6 +497,11 @@ class SandboxManager:
337
497
  f"Error cleaning up container {key}: {e}",
338
498
  )
339
499
 
500
+ @remote_wrapper_async()
501
+ async def cleanup_async(self, *args, **kwargs):
502
+ """Async wrapper for cleanup()."""
503
+ return await asyncio.to_thread(self.cleanup, *args, **kwargs)
504
+
340
505
  @remote_wrapper()
341
506
  def create_from_pool(self, sandbox_type=None, meta: Optional[Dict] = None):
342
507
  """Try to get a container from runtime pool"""
@@ -444,6 +609,11 @@ class SandboxManager:
444
609
  logger.debug(f"{e}: {traceback.format_exc()}")
445
610
  return self.create()
446
611
 
612
+ @remote_wrapper_async()
613
+ async def create_from_pool_async(self, *args, **kwargs):
614
+ """Async wrapper for create_from_pool()."""
615
+ return await asyncio.to_thread(self.create_from_pool, *args, **kwargs)
616
+
447
617
  @remote_wrapper()
448
618
  def create(
449
619
  self,
@@ -621,6 +791,11 @@ class SandboxManager:
621
791
  self.release(identity=container_name)
622
792
  return None
623
793
 
794
+ @remote_wrapper_async()
795
+ async def create_async(self, *args, **kwargs):
796
+ """Async wrapper for create()."""
797
+ return await asyncio.to_thread(self.create, *args, **kwargs)
798
+
624
799
  @remote_wrapper()
625
800
  def release(self, identity):
626
801
  try:
@@ -671,6 +846,11 @@ class SandboxManager:
671
846
  logger.debug(f"{traceback.format_exc()}")
672
847
  return False
673
848
 
849
+ @remote_wrapper_async()
850
+ async def release_async(self, *args, **kwargs):
851
+ """Async wrapper for release()."""
852
+ return await asyncio.to_thread(self.release, *args, **kwargs)
853
+
674
854
  @remote_wrapper()
675
855
  def start(self, identity):
676
856
  try:
@@ -703,6 +883,11 @@ class SandboxManager:
703
883
  )
704
884
  return False
705
885
 
886
+ @remote_wrapper_async()
887
+ async def start_async(self, *args, **kwargs):
888
+ """Async wrapper for start()."""
889
+ return await asyncio.to_thread(self.start, *args, **kwargs)
890
+
706
891
  @remote_wrapper()
707
892
  def stop(self, identity):
708
893
  try:
@@ -733,11 +918,21 @@ class SandboxManager:
733
918
  )
734
919
  return False
735
920
 
921
+ @remote_wrapper_async()
922
+ async def stop_async(self, *args, **kwargs):
923
+ """Async wrapper for stop()."""
924
+ return await asyncio.to_thread(self.stop, *args, **kwargs)
925
+
736
926
  @remote_wrapper()
737
927
  def get_status(self, identity):
738
928
  """Get container status by container_name or container_id."""
739
929
  return self.client.get_status(identity)
740
930
 
931
+ @remote_wrapper_async()
932
+ async def get_status_async(self, *args, **kwargs):
933
+ """Async wrapper for get_status()."""
934
+ return await asyncio.to_thread(self.get_status, *args, **kwargs)
935
+
741
936
  @remote_wrapper()
742
937
  def get_info(self, identity):
743
938
  """Get container information by container_name or container_id."""
@@ -753,6 +948,11 @@ class SandboxManager:
753
948
 
754
949
  return container_model
755
950
 
951
+ @remote_wrapper_async()
952
+ async def get_info_async(self, *args, **kwargs):
953
+ """Async wrapper for get_info()."""
954
+ return await asyncio.to_thread(self.get_info, *args, **kwargs)
955
+
756
956
  def _establish_connection(self, identity):
757
957
  container_model = ContainerModel(**self.get_info(identity))
758
958
 
@@ -769,24 +969,54 @@ class SandboxManager:
769
969
  container_model,
770
970
  ).__enter__()
771
971
 
972
+ async def _establish_connection_async(self, identity):
973
+ container_model = ContainerModel(**self.get_info(identity))
974
+
975
+ # TODO: TrainingSandboxClient lacks async, can use asyncio.to_thread()
976
+ if (
977
+ "sandbox-appworld" in container_model.version
978
+ or "sandbox-bfcl" in container_model.version
979
+ ):
980
+ client = TrainingSandboxClient(base_url=container_model.url)
981
+ return client.__enter__()
982
+ async_client = SandboxHttpAsyncClient(container_model)
983
+ await async_client.__aenter__()
984
+ return async_client
985
+
772
986
  @remote_wrapper()
773
987
  def check_health(self, identity):
774
988
  """List tool"""
775
989
  client = self._establish_connection(identity)
776
990
  return client.check_health()
777
991
 
992
+ @remote_wrapper_async()
993
+ async def check_health_async(self, identity):
994
+ client = await self._establish_connection_async(identity)
995
+ return await client.check_health()
996
+
778
997
  @remote_wrapper()
779
998
  def list_tools(self, identity, tool_type=None, **kwargs):
780
999
  """List tool"""
781
1000
  client = self._establish_connection(identity)
782
1001
  return client.list_tools(tool_type=tool_type, **kwargs)
783
1002
 
1003
+ @remote_wrapper_async()
1004
+ async def list_tools_async(self, identity, tool_type=None, **kwargs):
1005
+ client = await self._establish_connection_async(identity)
1006
+ return await client.list_tools(tool_type=tool_type, **kwargs)
1007
+
784
1008
  @remote_wrapper()
785
1009
  def call_tool(self, identity, tool_name=None, arguments=None):
786
1010
  """Call tool"""
787
1011
  client = self._establish_connection(identity)
788
1012
  return client.call_tool(tool_name, arguments)
789
1013
 
1014
+ @remote_wrapper_async()
1015
+ async def call_tool_async(self, identity, tool_name=None, arguments=None):
1016
+ """Call tool (async)"""
1017
+ client = await self._establish_connection_async(identity)
1018
+ return await client.call_tool(tool_name, arguments)
1019
+
790
1020
  @remote_wrapper()
791
1021
  def add_mcp_servers(self, identity, server_configs, overwrite=False):
792
1022
  """
@@ -798,11 +1028,36 @@ class SandboxManager:
798
1028
  overwrite=overwrite,
799
1029
  )
800
1030
 
1031
+ @remote_wrapper_async()
1032
+ async def add_mcp_servers_async(
1033
+ self,
1034
+ identity,
1035
+ server_configs,
1036
+ overwrite=False,
1037
+ ):
1038
+ """
1039
+ Add MCP servers to runtime (async).
1040
+ """
1041
+ client = await self._establish_connection_async(identity)
1042
+ return await client.add_mcp_servers(
1043
+ server_configs=server_configs,
1044
+ overwrite=overwrite,
1045
+ )
1046
+
801
1047
  @remote_wrapper()
802
1048
  def get_session_mapping(self, session_ctx_id: str) -> list:
803
1049
  """Get all container names bound to a session context"""
804
1050
  return self.session_mapping.get(session_ctx_id) or []
805
1051
 
1052
+ @remote_wrapper_async()
1053
+ async def get_session_mapping_async(self, *args, **kwargs):
1054
+ """Async wrapper for get_session_mapping()."""
1055
+ return await asyncio.to_thread(
1056
+ self.get_session_mapping,
1057
+ *args,
1058
+ **kwargs,
1059
+ )
1060
+
806
1061
  @remote_wrapper()
807
1062
  def list_session_keys(self) -> list:
808
1063
  """Return all session_ctx_id keys currently in mapping"""
@@ -810,3 +1065,8 @@ class SandboxManager:
810
1065
  for key in self.session_mapping.scan():
811
1066
  session_keys.append(key)
812
1067
  return session_keys
1068
+
1069
+ @remote_wrapper_async()
1070
+ async def list_session_keys_async(self, *args, **kwargs):
1071
+ """Async wrapper for list_session_keys()."""
1072
+ return await asyncio.to_thread(self.list_session_keys, *args, **kwargs)
@@ -245,7 +245,13 @@ async def proxy_vnc_static(sandbox_id: str, path: str):
245
245
  target_url = f"{base_url}/{path}"
246
246
 
247
247
  try:
248
- async with httpx.AsyncClient() as client:
248
+ async with httpx.AsyncClient(
249
+ headers={ # For FC
250
+ "Content-Type": "application/json",
251
+ "x-agentrun-session-id": "s" + sandbox_id,
252
+ "x-agentscope-runtime-session-id": "s" + sandbox_id,
253
+ },
254
+ ) as client:
249
255
  upstream = await client.get(target_url)
250
256
  return Response(
251
257
  content=upstream.content,
@@ -1,2 +1,2 @@
1
1
  # -*- coding: utf-8 -*-
2
- __version__ = "v1.0.2"
2
+ __version__ = "v1.0.4"