agentscope-runtime 1.0.1__py3-none-any.whl → 1.0.2__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/message.py +32 -7
  2. agentscope_runtime/adapters/agentscope/stream.py +121 -91
  3. agentscope_runtime/adapters/agno/__init__.py +0 -0
  4. agentscope_runtime/adapters/agno/message.py +30 -0
  5. agentscope_runtime/adapters/agno/stream.py +122 -0
  6. agentscope_runtime/adapters/langgraph/__init__.py +12 -0
  7. agentscope_runtime/adapters/langgraph/message.py +257 -0
  8. agentscope_runtime/adapters/langgraph/stream.py +205 -0
  9. agentscope_runtime/cli/__init__.py +7 -0
  10. agentscope_runtime/cli/cli.py +63 -0
  11. agentscope_runtime/cli/commands/__init__.py +2 -0
  12. agentscope_runtime/cli/commands/chat.py +815 -0
  13. agentscope_runtime/cli/commands/deploy.py +1062 -0
  14. agentscope_runtime/cli/commands/invoke.py +58 -0
  15. agentscope_runtime/cli/commands/list_cmd.py +103 -0
  16. agentscope_runtime/cli/commands/run.py +176 -0
  17. agentscope_runtime/cli/commands/sandbox.py +128 -0
  18. agentscope_runtime/cli/commands/status.py +60 -0
  19. agentscope_runtime/cli/commands/stop.py +185 -0
  20. agentscope_runtime/cli/commands/web.py +166 -0
  21. agentscope_runtime/cli/loaders/__init__.py +6 -0
  22. agentscope_runtime/cli/loaders/agent_loader.py +295 -0
  23. agentscope_runtime/cli/state/__init__.py +10 -0
  24. agentscope_runtime/cli/utils/__init__.py +18 -0
  25. agentscope_runtime/cli/utils/console.py +378 -0
  26. agentscope_runtime/cli/utils/validators.py +118 -0
  27. agentscope_runtime/engine/app/agent_app.py +7 -4
  28. agentscope_runtime/engine/deployers/__init__.py +1 -0
  29. agentscope_runtime/engine/deployers/agentrun_deployer.py +152 -22
  30. agentscope_runtime/engine/deployers/base.py +27 -2
  31. agentscope_runtime/engine/deployers/kubernetes_deployer.py +158 -31
  32. agentscope_runtime/engine/deployers/local_deployer.py +188 -25
  33. agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
  34. agentscope_runtime/engine/deployers/state/__init__.py +9 -0
  35. agentscope_runtime/engine/deployers/state/manager.py +388 -0
  36. agentscope_runtime/engine/deployers/state/schema.py +96 -0
  37. agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
  38. agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
  39. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
  40. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +15 -8
  41. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +30 -2
  42. agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
  43. agentscope_runtime/engine/deployers/utils/package.py +56 -6
  44. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +16 -2
  45. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
  46. agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
  47. agentscope_runtime/engine/runner.py +25 -6
  48. agentscope_runtime/engine/schemas/exception.py +580 -0
  49. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
  50. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
  51. agentscope_runtime/sandbox/utils.py +2 -0
  52. agentscope_runtime/version.py +1 -1
  53. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/METADATA +24 -7
  54. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/RECORD +58 -28
  55. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/entry_points.txt +1 -0
  56. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/WHEEL +0 -0
  57. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/licenses/LICENSE +0 -0
  58. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ import logging
7
7
  import os
8
8
  import time
9
9
  from dataclasses import dataclass
10
+ from datetime import datetime
10
11
  from pathlib import Path
11
12
  from typing import Dict, Optional, List, Any, Union, Tuple
12
13
 
@@ -34,7 +35,9 @@ from pydantic import BaseModel, Field
34
35
  from .adapter.protocol_adapter import ProtocolAdapter
35
36
  from .base import DeployManager
36
37
  from .local_deployer import LocalDeployManager
38
+ from .state import Deployment
37
39
  from .utils.detached_app import get_bundle_entry_script
40
+ from .utils.package import generate_build_directory
38
41
  from .utils.wheel_packager import (
39
42
  default_deploy_name,
40
43
  generate_wrapper_project,
@@ -95,7 +98,7 @@ class AgentRunConfig(BaseModel):
95
98
 
96
99
  execution_role_arn: Optional[str] = None
97
100
 
98
- session_concurrency_limit: Optional[int] = 1
101
+ session_concurrency_limit: Optional[int] = 200
99
102
  session_idle_timeout_seconds: Optional[int] = 3600
100
103
 
101
104
  @classmethod
@@ -147,7 +150,7 @@ class AgentRunConfig(BaseModel):
147
150
 
148
151
  session_concurrency_limit_str = os.environ.get(
149
152
  "AGENT_RUN_SESSION_CONCURRENCY_LIMIT",
150
- "1",
153
+ "200",
151
154
  )
152
155
  session_idle_timeout_seconds_str = os.environ.get(
153
156
  "AGENT_RUN_SESSION_IDLE_TIMEOUT_SECONDS",
@@ -218,6 +221,7 @@ class OSSConfig(BaseModel):
218
221
  region: str = Field("cn-hangzhou", description="OSS region")
219
222
  access_key_id: Optional[str] = None
220
223
  access_key_secret: Optional[str] = None
224
+ bucket_name: str
221
225
 
222
226
  @classmethod
223
227
  def from_env(cls) -> "OSSConfig":
@@ -236,6 +240,7 @@ class OSSConfig(BaseModel):
236
240
  "OSS_ACCESS_KEY_SECRET",
237
241
  os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET"),
238
242
  ),
243
+ bucket_name=os.environ.get("OSS_BUCKET_NAME"),
239
244
  )
240
245
 
241
246
  def ensure_valid(self) -> None:
@@ -245,10 +250,14 @@ class OSSConfig(BaseModel):
245
250
  RuntimeError: If required AccessKey credentials are missing.
246
251
  """
247
252
  # Allow fallback to Alibaba Cloud AK/SK via from_env()
248
- if not self.access_key_id or not self.access_key_secret:
253
+ if (
254
+ not self.access_key_id
255
+ or not self.access_key_secret
256
+ or not self.bucket_name
257
+ ):
249
258
  raise RuntimeError(
250
259
  "Missing AccessKey for OSS. Set either OSS_ACCESS_KEY_ID/OSS_ACCESS_KEY_SECRET "
251
- "or ALIBABA_CLOUD_ACCESS_KEY_ID/ALIBABA_CLOUD_ACCESS_KEY_SECRET.",
260
+ "or ALIBABA_CLOUD_ACCESS_KEY_ID/ALIBABA_CLOUD_ACCESS_KEY_SECRET or OSS_BUCKET_NAME.",
252
261
  )
253
262
 
254
263
 
@@ -278,6 +287,7 @@ class AgentRunDeployManager(DeployManager):
278
287
  oss_config: Optional[OSSConfig] = None,
279
288
  agentrun_config: Optional[AgentRunConfig] = None,
280
289
  build_root: Optional[Union[str, Path]] = None,
290
+ state_manager=None,
281
291
  ):
282
292
  """Initialize AgentRun deployment manager.
283
293
 
@@ -285,8 +295,9 @@ class AgentRunDeployManager(DeployManager):
285
295
  oss_config: OSS configuration for artifact storage. If None, loads from environment.
286
296
  agentrun_config: AgentRun service configuration. If None, loads from environment.
287
297
  build_root: Root directory for build artifacts. If None, uses parent directory of current working directory.
298
+ state_manager: Deployment state manager. If None, creates a new instance.
288
299
  """
289
- super().__init__()
300
+ super().__init__(state_manager=state_manager)
290
301
  self.oss_config = oss_config or OSSConfig.from_env()
291
302
  self.agentrun_config = agentrun_config or AgentRunConfig.from_env()
292
303
  self.build_root = (
@@ -416,18 +427,19 @@ class AgentRunDeployManager(DeployManager):
416
427
  )
417
428
 
418
429
  name = deploy_name or default_deploy_name()
419
- proj_root = project_dir.resolve()
430
+
431
+ # Generate build directory with platform-aware naming
432
+ # proj_root = project_dir.resolve()
420
433
  if isinstance(self.build_root, Path):
421
434
  effective_build_root = self.build_root.resolve()
422
435
  else:
423
436
  if self.build_root:
424
437
  effective_build_root = Path(self.build_root).resolve()
425
438
  else:
426
- effective_build_root = (
427
- proj_root.parent / ".agentscope_runtime_builds"
428
- ).resolve()
439
+ # Use centralized directory generation function
440
+ effective_build_root = generate_build_directory("agentrun")
429
441
 
430
- build_dir = effective_build_root / f"build-{int(time.time())}"
442
+ build_dir = effective_build_root
431
443
  build_dir.mkdir(parents=True, exist_ok=True)
432
444
 
433
445
  logger.info("Generating wrapper project: %s", name)
@@ -442,6 +454,7 @@ class AgentRunDeployManager(DeployManager):
442
454
  logger.info("Building wheel package from: %s", wrapper_project_dir)
443
455
  wheel_path = build_wheel(wrapper_project_dir)
444
456
  logger.info("Wheel package created: %s", wheel_path)
457
+
445
458
  return wheel_path, name
446
459
 
447
460
  def _generate_env_file(
@@ -560,9 +573,14 @@ class AgentRunDeployManager(DeployManager):
560
573
  FileNotFoundError: If specified files/directories don't exist.
561
574
  """
562
575
  if not agentrun_id:
563
- if not runner and not project_dir and not external_whl_path:
576
+ if (
577
+ not app
578
+ and not runner
579
+ and not project_dir
580
+ and not external_whl_path
581
+ ):
564
582
  raise ValueError(
565
- "Must provide either runner, project_dir, or external_whl_path",
583
+ "Must provide either app, runner, project_dir, or external_whl_path",
566
584
  )
567
585
  try:
568
586
  if runner or app:
@@ -579,6 +597,7 @@ class AgentRunDeployManager(DeployManager):
579
597
  protocol_adapters=protocol_adapters,
580
598
  requirements=requirements,
581
599
  extra_packages=extra_packages,
600
+ platform="agentrun",
582
601
  **kwargs,
583
602
  )
584
603
  if project_dir:
@@ -647,7 +666,7 @@ class AgentRunDeployManager(DeployManager):
647
666
  logger.info("Uploading zip package to OSS")
648
667
  oss_result = await self._upload_to_fixed_oss_bucket(
649
668
  zip_file_path=zip_file_path,
650
- bucket_name="tmp-agentscope-agentrun-code",
669
+ bucket_name=self.oss_config.bucket_name,
651
670
  )
652
671
  logger.info("Zip package uploaded to OSS successfully")
653
672
 
@@ -661,21 +680,50 @@ class AgentRunDeployManager(DeployManager):
661
680
  environment=environment,
662
681
  )
663
682
 
683
+ # Use base class UUID deploy_id (already set in __init__)
684
+ deploy_id = self.deploy_id
685
+ agent_runtime_id = agentrun_deploy_result["agent_runtime_id"]
686
+ endpoint_url = agentrun_deploy_result.get(
687
+ "agent_runtime_public_endpoint_url",
688
+ "",
689
+ )
690
+ console_url = (
691
+ f"https://functionai.console.aliyun.com/{self.agentrun_config.region_id}/"
692
+ f"agent/infra/agent-runtime/agent-detail?id={agent_runtime_id}"
693
+ )
694
+
695
+ # Save deployment to state manager
696
+ deployment = Deployment(
697
+ id=deploy_id,
698
+ platform="agentrun",
699
+ url=console_url,
700
+ status="running",
701
+ created_at=datetime.now().isoformat(),
702
+ agent_source=kwargs.get("agent_source"),
703
+ config={
704
+ "agent_runtime_id": agent_runtime_id,
705
+ "agent_runtime_endpoint_url": endpoint_url,
706
+ "resource_name": name,
707
+ "wheel_path": str(wheel_path),
708
+ "artifact_url": oss_result.get("presigned_url", ""),
709
+ "region_id": self.agentrun_config.region_id,
710
+ },
711
+ )
712
+ self.state_manager.save(deployment)
713
+
664
714
  # Return deployment results
665
715
  logger.info(
666
716
  "Deployment completed successfully. Agent runtime ID: %s",
667
- agentrun_deploy_result["agent_runtime_id"],
717
+ agent_runtime_id,
668
718
  )
669
719
  return {
670
720
  "message": "Agent deployed successfully to AgentRun",
671
- "agentrun_id": agentrun_deploy_result["agent_runtime_id"],
672
- "agentrun_endpoint_url": agentrun_deploy_result[
673
- "agent_runtime_public_endpoint_url"
674
- ],
721
+ "agentrun_id": agent_runtime_id,
722
+ "agentrun_endpoint_url": endpoint_url,
675
723
  "wheel_path": str(wheel_path),
676
- "artifact_url": oss_result["presigned_url"],
677
- "url": f'https://functionai.console.aliyun.com/{self.agentrun_config.region_id}/agent/infra/agent-runtime/agent-detail?id={agentrun_deploy_result["agent_runtime_id"]}',
678
- "deploy_id": agentrun_deploy_result["agent_runtime_id"],
724
+ "artifact_url": oss_result.get("presigned_url", ""),
725
+ "url": console_url,
726
+ "deploy_id": deploy_id,
679
727
  "resource_name": name,
680
728
  }
681
729
 
@@ -861,7 +909,6 @@ ls -lh /output/{zip_filename}
861
909
  from alibabacloud_oss_v2.credentials import (
862
910
  StaticCredentialsProvider,
863
911
  )
864
- import datetime
865
912
  except ImportError as e:
866
913
  logger.error(
867
914
  "OSS SDK not available. Install with: pip install alibabacloud-oss-v2",
@@ -2539,3 +2586,86 @@ ls -lh /output/{zip_filename}
2539
2586
  "error": str(e),
2540
2587
  "message": f"Exception occurred while publishing agent runtime version: {str(e)}",
2541
2588
  }
2589
+
2590
+ async def stop(self, deploy_id: str, **kwargs) -> Dict[str, Any]:
2591
+ """Stop AgentRun deployment by deleting it.
2592
+
2593
+ Args:
2594
+ deploy_id: AgentRun runtime ID (agent_runtime_id)
2595
+ **kwargs: Additional parameters
2596
+
2597
+ Returns:
2598
+ Dict with success status, message, and details
2599
+ """
2600
+ try:
2601
+ # Try to get deployment info from state for context
2602
+ deployment_info = None
2603
+ deployment = None
2604
+ try:
2605
+ deployment = self.state_manager.get(deploy_id)
2606
+ if deployment:
2607
+ deployment_info = {
2608
+ "url": deployment.url
2609
+ if hasattr(deployment, "url")
2610
+ else None,
2611
+ "resource_name": getattr(
2612
+ deployment,
2613
+ "resource_name",
2614
+ None,
2615
+ ),
2616
+ }
2617
+ logger.debug(
2618
+ f"Fetched deployment info from state: {deployment_info}",
2619
+ )
2620
+ except Exception as e:
2621
+ logger.debug(
2622
+ f"Could not fetch deployment info from state: {e}",
2623
+ )
2624
+
2625
+ logger.info(f"Stopping AgentRun deployment: {deploy_id}")
2626
+
2627
+ # Get agent_runtime_id from deployment config
2628
+ agent_runtime_id = None
2629
+ if deployment and deployment.config:
2630
+ agent_runtime_id = deployment.config.get("agent_runtime_id")
2631
+
2632
+ if not agent_runtime_id:
2633
+ # Fallback: try using deploy_id as agent_runtime_id for backward compatibility
2634
+ agent_runtime_id = deploy_id
2635
+ logger.warning(
2636
+ f"Could not find agent_runtime_id in deployment config, "
2637
+ f"using deploy_id as fallback: {deploy_id}",
2638
+ )
2639
+
2640
+ # Use the existing delete method with agent_runtime_id
2641
+ result = await self.delete(agent_runtime_id)
2642
+
2643
+ if result.get("success"):
2644
+ # Remove from state manager on successful deletion
2645
+ try:
2646
+ self.state_manager.update_status(deploy_id, "stopped")
2647
+ except KeyError:
2648
+ logger.debug(
2649
+ f"Deployment {deploy_id} not found in state (already removed)",
2650
+ )
2651
+
2652
+ return {
2653
+ "success": True,
2654
+ "message": f"AgentRun deployment {deploy_id} deleted successfully",
2655
+ "details": result,
2656
+ }
2657
+ else:
2658
+ return {
2659
+ "success": False,
2660
+ "message": f"Failed to delete AgentRun deployment: {result.get('message', 'Unknown error')}",
2661
+ "details": result,
2662
+ }
2663
+ except Exception as e:
2664
+ logger.error(
2665
+ f"Failed to stop AgentRun deployment {deploy_id}: {e}",
2666
+ )
2667
+ return {
2668
+ "success": False,
2669
+ "message": f"Failed to stop AgentRun deployment: {e}",
2670
+ "details": {"deploy_id": deploy_id, "error": str(e)},
2671
+ }
@@ -1,18 +1,43 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import uuid
3
3
  from abc import abstractmethod, ABC
4
- from typing import Dict
4
+ from typing import Dict, Any
5
5
 
6
6
 
7
7
  # there is not many attributes in it, consider it as interface, instead of
8
8
  # pydantic BaseModel
9
9
  class DeployManager(ABC):
10
- def __init__(self):
10
+ def __init__(self, state_manager=None):
11
11
  self.deploy_id = str(uuid.uuid4())
12
12
  self._app = None
13
13
 
14
+ # Initialize state manager - shared across all deployers
15
+ if state_manager is None:
16
+ from agentscope_runtime.engine.deployers.state import (
17
+ DeploymentStateManager,
18
+ )
19
+
20
+ state_manager = DeploymentStateManager()
21
+ self.state_manager = state_manager
22
+
14
23
  @abstractmethod
15
24
  async def deploy(self, *args, **kwargs) -> Dict[str, str]:
16
25
  """Deploy the service and return a dictionary with deploy_id and
17
26
  URL."""
18
27
  raise NotImplementedError
28
+
29
+ @abstractmethod
30
+ async def stop(self, deploy_id: str, **kwargs) -> Dict[str, Any]:
31
+ """Stop the deployed service.
32
+
33
+ Args:
34
+ deploy_id: Deployment identifier
35
+ **kwargs: Platform-specific parameters (url, namespace, etc.)
36
+
37
+ Returns:
38
+ Dict with keys:
39
+ - success (bool): Whether stop succeeded
40
+ - message (str): Status message
41
+ - details (dict, optional): Platform-specific details
42
+ """
43
+ raise NotImplementedError
@@ -1,17 +1,19 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import logging
3
3
  import os
4
- import time
4
+ from datetime import datetime
5
5
  from typing import Optional, Dict, List, Union, Any
6
6
 
7
7
  from pydantic import BaseModel, Field
8
8
 
9
+ from agentscope_runtime.engine.deployers.state import Deployment
9
10
  from .adapter.protocol_adapter import ProtocolAdapter
10
11
  from .base import DeployManager
11
12
  from .utils.docker_image_utils import (
12
13
  ImageFactory,
13
14
  RegistryConfig,
14
15
  )
16
+ from .utils.k8s_utils import isLocalK8sEnvironment
15
17
  from ...common.container_clients.kubernetes_client import (
16
18
  KubernetesClient,
17
19
  )
@@ -36,7 +38,7 @@ class K8sConfig(BaseModel):
36
38
  class BuildConfig(BaseModel):
37
39
  """Build configuration"""
38
40
 
39
- build_context_dir: str = "/tmp/k8s_build"
41
+ build_context_dir: Optional[str] = None # None allows caching
40
42
  dockerfile_template: str = None
41
43
  build_timeout: int = 600 # 10 minutes
42
44
  push_timeout: int = 300 # 5 minutes
@@ -51,15 +53,15 @@ class KubernetesDeployManager(DeployManager):
51
53
  kube_config: K8sConfig = None,
52
54
  registry_config: RegistryConfig = RegistryConfig(),
53
55
  use_deployment: bool = True,
54
- build_context_dir: str = "/tmp/k8s_build",
56
+ build_context_dir: Optional[str] = None,
57
+ state_manager=None,
55
58
  ):
56
- super().__init__()
59
+ super().__init__(state_manager=state_manager)
57
60
  self.kubeconfig = kube_config
58
61
  self.registry_config = registry_config
59
62
  self.image_factory = ImageFactory()
60
63
  self.use_deployment = use_deployment
61
64
  self.build_context_dir = build_context_dir
62
- self._deployed_resources = {}
63
65
  self._built_images = {}
64
66
 
65
67
  self.k8s_client = KubernetesClient(
@@ -67,10 +69,65 @@ class KubernetesDeployManager(DeployManager):
67
69
  image_registry=self.registry_config.get_full_url(),
68
70
  )
69
71
 
72
+ @staticmethod
73
+ def get_service_endpoint(
74
+ service_external_ip: Optional[str],
75
+ service_port: Optional[Union[int, list]],
76
+ fallback_host: str = "127.0.0.1",
77
+ ) -> str:
78
+ """
79
+ Auto-select appropriate service endpoint based on detected environment.
80
+
81
+ Solves the common issue where Kubernetes LoadBalancer/ExternalIP is not
82
+ reachable from localhost in local clusters (e.g., Minikube/Kind).
83
+
84
+ Args:
85
+ service_external_ip: ExternalIP or LoadBalancer IP from Service
86
+ service_port: Target port
87
+ fallback_host: Host to use in local environments (default:
88
+ 127.0.0.1)
89
+
90
+ Returns:
91
+ str: Full HTTP endpoint URL: http://<host>:<port>
92
+
93
+ Example:
94
+ >>> endpoint = get_service_endpoint('192.168.5.1', 8080)
95
+ >>> # In local env → 'http://127.0.0.1:8080'
96
+ >>> # In cloud env → 'http://192.168.5.1:8080'
97
+ """
98
+ if not service_external_ip:
99
+ service_external_ip = "127.0.0.1"
100
+
101
+ if not service_port:
102
+ service_port = 8080
103
+
104
+ if isinstance(service_port, list):
105
+ service_port = service_port[0]
106
+
107
+ if isLocalK8sEnvironment():
108
+ host = fallback_host
109
+ logger.info(
110
+ f"Local K8s environment detected; using {host} instead of "
111
+ f"{service_external_ip}",
112
+ )
113
+ else:
114
+ host = service_external_ip
115
+ logger.info(
116
+ f"Cloud/remote environment detected; using External IP: "
117
+ f"{host}",
118
+ )
119
+
120
+ return f"http://{host}:{service_port}"
121
+
122
+ @staticmethod
123
+ def get_resource_name(deploy_id: str) -> str:
124
+ return f"agent-{deploy_id[:8]}"
125
+
70
126
  async def deploy(
71
127
  self,
72
128
  app=None,
73
129
  runner=None,
130
+ entrypoint: Optional[str] = None,
74
131
  endpoint_path: str = "/process",
75
132
  stream: bool = True,
76
133
  custom_endpoints: Optional[List[Dict]] = None,
@@ -86,15 +143,19 @@ class KubernetesDeployManager(DeployManager):
86
143
  image_name: str = "agent_llm",
87
144
  image_tag: str = "latest",
88
145
  push_to_registry: bool = False,
146
+ use_cache: bool = True,
89
147
  **kwargs,
90
148
  ) -> Dict[str, Any]:
91
149
  """
92
150
  Deploy runner to Kubernetes.
93
151
 
152
+ All temporary files are created in cwd/.agentscope_runtime/ by default.
153
+
94
154
  Args:
95
155
  app: Agent app to be deployed
96
156
  runner: Complete Runner object with agent, environment_manager,
97
157
  context_manager
158
+ entrypoint: Entrypoint spec (e.g., "app.py" or "app.py:handler")
98
159
  endpoint_path: API endpoint path
99
160
  stream: Enable streaming responses
100
161
  custom_endpoints: Custom endpoints from agent app
@@ -108,6 +169,7 @@ class KubernetesDeployManager(DeployManager):
108
169
  environment: Environment variables dict
109
170
  mount_dir: Mount directory
110
171
  runtime_config: K8s runtime configuration
172
+ use_cache: Enable build cache (default: True)
111
173
  # Backward compatibility
112
174
  image_name: Image name
113
175
  image_tag: Image tag
@@ -132,6 +194,7 @@ class KubernetesDeployManager(DeployManager):
132
194
  built_image_name = self.image_factory.build_image(
133
195
  app=app,
134
196
  runner=runner,
197
+ entrypoint=entrypoint,
135
198
  requirements=requirements,
136
199
  extra_packages=extra_packages or [],
137
200
  base_image=base_image,
@@ -145,6 +208,7 @@ class KubernetesDeployManager(DeployManager):
145
208
  port=port,
146
209
  protocol_adapters=protocol_adapters,
147
210
  custom_endpoints=custom_endpoints,
211
+ use_cache=use_cache,
148
212
  **kwargs,
149
213
  )
150
214
  if not built_image_name:
@@ -173,7 +237,7 @@ class KubernetesDeployManager(DeployManager):
173
237
  else:
174
238
  volume_bindings = {}
175
239
 
176
- resource_name = f"agent-{deploy_id[:8]}"
240
+ resource_name = self.get_resource_name(deploy_id)
177
241
 
178
242
  logger.info(f"Building kubernetes deployment for {deploy_id}")
179
243
 
@@ -197,33 +261,36 @@ class KubernetesDeployManager(DeployManager):
197
261
  )
198
262
 
199
263
  if ports:
200
- url = f"http://{ip}:{ports[0]}"
264
+ url = self.get_service_endpoint(ip, ports)
201
265
  else:
202
- url = f"http://{ip}:8080"
266
+ url = self.get_service_endpoint(ip, port)
203
267
 
204
268
  logger.info(f"Deployment {deploy_id} successful: {url}")
205
269
 
206
- self._deployed_resources[deploy_id] = {
207
- "resource_name": resource_name,
208
- "service_name": _id,
209
- "image": built_image_name,
210
- "created_at": time.time(),
211
- "replicas": replicas if self.use_deployment else 1,
212
- "config": {
213
- "runner": runner.__class__.__name__,
270
+ deployment = Deployment(
271
+ id=deploy_id,
272
+ platform="k8s",
273
+ url=url,
274
+ status="running",
275
+ created_at=datetime.now().isoformat(),
276
+ agent_source=kwargs.get("agent_source"),
277
+ config={
278
+ "service_name": _id,
279
+ "image": built_image_name,
280
+ "replicas": replicas if self.use_deployment else 1,
281
+ "runner": runner.__class__.__name__ if runner else None,
214
282
  "extra_packages": extra_packages,
215
- "requirements": requirements, # New format
283
+ "requirements": requirements,
216
284
  "base_image": base_image,
217
285
  "port": port,
218
- "replicas": replicas,
219
286
  "environment": environment,
220
287
  "runtime_config": runtime_config,
221
288
  "endpoint_path": endpoint_path,
222
289
  "stream": stream,
223
- "protocol_adapters": protocol_adapters,
224
- **kwargs,
225
290
  },
226
- }
291
+ )
292
+ self.state_manager.save(deployment)
293
+
227
294
  return {
228
295
  "deploy_id": deploy_id,
229
296
  "url": url,
@@ -240,21 +307,81 @@ class KubernetesDeployManager(DeployManager):
240
307
  f"Deployment failed: {e}, {traceback.format_exc()}",
241
308
  ) from e
242
309
 
243
- async def stop(self) -> bool:
244
- """Stop service"""
245
- if self.deploy_id not in self._deployed_resources:
246
- return False
310
+ async def stop(
311
+ self,
312
+ deploy_id: str,
313
+ **kwargs,
314
+ ) -> Dict[str, Any]:
315
+ """Stop Kubernetes deployment.
316
+
317
+ Args:
318
+ deploy_id: Deployment identifier
319
+ **kwargs: Additional parameters
320
+
321
+ Returns:
322
+ Dict with success status, message, and details
323
+ """
324
+ # Derive resource name from deploy_id
325
+ resource_name = self.get_resource_name(deploy_id)
326
+
327
+ try:
328
+ # Try to remove the deployment
329
+ success = self.k8s_client.remove_deployment(resource_name)
330
+
331
+ if success:
332
+ # Remove from state manager
333
+ try:
334
+ self.state_manager.update_status(deploy_id, "stopped")
335
+ except KeyError:
336
+ logger.debug(
337
+ f"Deployment {deploy_id} not found "
338
+ f"in state (already removed)",
339
+ )
247
340
 
248
- resources = self._deployed_resources[self.deploy_id]
249
- service_name = resources["service_name"]
250
- return self.k8s_client.remove_deployment(service_name)
341
+ return {
342
+ "success": True,
343
+ "message": f"Kubernetes deployment {resource_name} "
344
+ f"removed",
345
+ "details": {
346
+ "deploy_id": deploy_id,
347
+ "resource_name": resource_name,
348
+ },
349
+ }
350
+ else:
351
+ return {
352
+ "success": False,
353
+ "message": f"Kubernetes deployment {resource_name} not "
354
+ f"found (may already be deleted), Please check the "
355
+ f"detail in cluster",
356
+ "details": {
357
+ "deploy_id": deploy_id,
358
+ "resource_name": resource_name,
359
+ },
360
+ }
361
+ except Exception as e:
362
+ logger.error(
363
+ f"Failed to remove K8s deployment {resource_name}: {e}",
364
+ )
365
+ return {
366
+ "success": False,
367
+ "message": f"Failed to remove K8s deployment: {e}",
368
+ "details": {
369
+ "deploy_id": deploy_id,
370
+ "resource_name": resource_name,
371
+ "error": str(e),
372
+ },
373
+ }
251
374
 
252
375
  def get_status(self) -> str:
253
376
  """Get deployment status"""
254
- if self.deploy_id not in self._deployed_resources:
377
+ deployment = self.state_manager.get(self.deploy_id)
378
+ if not deployment:
255
379
  return "not_found"
256
380
 
257
- resources = self._deployed_resources[self.deploy_id]
258
- service_name = resources["service_name"]
381
+ # Get service_name from config
382
+ config = getattr(deployment, "config", {})
383
+ service_name = config.get("service_name")
384
+ if not service_name:
385
+ return "unknown"
259
386
 
260
387
  return self.k8s_client.get_deployment_status(service_name)