agentscope-runtime 0.1.6__py3-none-any.whl → 0.2.0__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 (87) hide show
  1. agentscope_runtime/common/container_clients/__init__.py +0 -0
  2. agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +546 -6
  3. agentscope_runtime/engine/__init__.py +12 -0
  4. agentscope_runtime/engine/agents/agentscope_agent.py +130 -10
  5. agentscope_runtime/engine/agents/agno_agent.py +8 -10
  6. agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
  7. agentscope_runtime/engine/app/__init__.py +6 -0
  8. agentscope_runtime/engine/app/agent_app.py +239 -0
  9. agentscope_runtime/engine/app/base_app.py +181 -0
  10. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  11. agentscope_runtime/engine/deployers/__init__.py +13 -0
  12. agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2890 -0
  14. agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
  15. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
  16. agentscope_runtime/engine/deployers/base.py +1 -0
  17. agentscope_runtime/engine/deployers/cli_fc_deploy.py +203 -0
  18. agentscope_runtime/engine/deployers/kubernetes_deployer.py +272 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +414 -501
  20. agentscope_runtime/engine/deployers/modelstudio_deployer.py +838 -0
  21. agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
  22. agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
  23. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
  24. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
  25. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
  26. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +306 -0
  27. agentscope_runtime/engine/deployers/utils/package_project_utils.py +1163 -0
  28. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
  29. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +1064 -0
  30. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
  31. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
  32. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
  33. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
  34. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  35. agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
  36. agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
  37. agentscope_runtime/engine/runner.py +76 -35
  38. agentscope_runtime/engine/schemas/agent_schemas.py +112 -2
  39. agentscope_runtime/engine/schemas/embedding.py +37 -0
  40. agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
  41. agentscope_runtime/engine/schemas/oai_llm.py +538 -0
  42. agentscope_runtime/engine/schemas/realtime.py +254 -0
  43. agentscope_runtime/engine/services/tablestore_memory_service.py +4 -1
  44. agentscope_runtime/engine/tracing/__init__.py +9 -3
  45. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  46. agentscope_runtime/engine/tracing/base.py +66 -34
  47. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  48. agentscope_runtime/engine/tracing/message_util.py +528 -0
  49. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  50. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  51. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  52. agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -1
  53. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -1
  54. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  55. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -1
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +2 -1
  57. agentscope_runtime/sandbox/box/training_box/training_box.py +0 -42
  58. agentscope_runtime/sandbox/client/http_client.py +52 -18
  59. agentscope_runtime/sandbox/constant.py +3 -0
  60. agentscope_runtime/sandbox/custom/custom_sandbox.py +2 -1
  61. agentscope_runtime/sandbox/custom/example.py +2 -1
  62. agentscope_runtime/sandbox/enums.py +0 -1
  63. agentscope_runtime/sandbox/manager/sandbox_manager.py +29 -22
  64. agentscope_runtime/sandbox/model/container.py +6 -0
  65. agentscope_runtime/sandbox/registry.py +1 -1
  66. agentscope_runtime/sandbox/tools/tool.py +4 -0
  67. agentscope_runtime/version.py +1 -1
  68. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +103 -59
  69. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +87 -52
  70. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +1 -0
  71. /agentscope_runtime/{sandbox/manager/container_clients → common}/__init__.py +0 -0
  72. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  73. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  74. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  75. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  76. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_mapping.py +0 -0
  77. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  78. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  79. /agentscope_runtime/{sandbox/manager → common}/collections/redis_mapping.py +0 -0
  80. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  81. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  82. /agentscope_runtime/{sandbox/manager → common}/container_clients/agentrun_client.py +0 -0
  83. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  84. /agentscope_runtime/{sandbox/manager → common}/container_clients/docker_client.py +0 -0
  85. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
  86. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
  87. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,12 @@
1
1
  # -*- coding: utf-8 -*-
2
- # pylint: disable=too-many-branches
3
- import os
2
+ # pylint: disable=too-many-branches,too-many-statements
4
3
  import time
5
4
  import hashlib
6
5
  import traceback
7
6
  import logging
8
7
 
8
+ from typing import Optional, Tuple
9
+
9
10
  from kubernetes import client
10
11
  from kubernetes import config as k8s_config
11
12
  from kubernetes.client.rest import ApiException
@@ -16,10 +17,15 @@ logger = logging.getLogger(__name__)
16
17
 
17
18
 
18
19
  class KubernetesClient(BaseClient):
19
- def __init__(self, config=None):
20
+ def __init__(
21
+ self,
22
+ config=None,
23
+ image_registry: Optional[str] = None,
24
+ ):
20
25
  self.config = config
21
26
  namespace = self.config.k8s_namespace
22
27
  kubeconfig = self.config.kubeconfig_path
28
+ self.image_registry = image_registry
23
29
  try:
24
30
  if kubeconfig:
25
31
  k8s_config.load_kube_config(config_file=kubeconfig)
@@ -31,6 +37,7 @@ class KubernetesClient(BaseClient):
31
37
  except k8s_config.ConfigException:
32
38
  k8s_config.load_kube_config()
33
39
  self.v1 = client.CoreV1Api()
40
+ self.apps_v1 = client.AppsV1Api() # For Deployments
34
41
  self.namespace = namespace
35
42
  # Test connection
36
43
  self.v1.list_namespace()
@@ -45,6 +52,61 @@ class KubernetesClient(BaseClient):
45
52
  "• For in-cluster: ensure proper RBAC permissions",
46
53
  ) from e
47
54
 
55
+ def _is_local_cluster(self):
56
+ """
57
+ Determine if we're connected to a local Kubernetes cluster.
58
+
59
+ Returns:
60
+ bool: True if connected to a local cluster, False otherwise
61
+ """
62
+ try:
63
+ # Get the current context configuration
64
+ contexts, current_context = k8s_config.list_kube_config_contexts(
65
+ config_file=self.config.kubeconfig_path
66
+ if hasattr(self.config, "kubeconfig_path")
67
+ and self.config.kubeconfig_path
68
+ else None,
69
+ )
70
+
71
+ if current_context and current_context.get("context"):
72
+ cluster_name = current_context["context"].get("cluster", "")
73
+ server = None
74
+
75
+ # Get cluster server URL
76
+ for cluster in contexts.get("clusters", []):
77
+ if cluster["name"] == cluster_name:
78
+ server = cluster.get("cluster", {}).get("server", "")
79
+ break
80
+
81
+ if server:
82
+ # Check for common local cluster patterns
83
+ local_patterns = [
84
+ "localhost",
85
+ "127.0.0.1",
86
+ "0.0.0.0",
87
+ "docker-desktop",
88
+ "kind-", # kind clusters
89
+ "minikube", # minikube
90
+ "k3d-", # k3d clusters
91
+ "colima", # colima
92
+ ]
93
+
94
+ server_lower = server.lower()
95
+ cluster_lower = cluster_name.lower()
96
+
97
+ for pattern in local_patterns:
98
+ if pattern in server_lower or pattern in cluster_lower:
99
+ return True
100
+
101
+ return False
102
+
103
+ except Exception as e:
104
+ logger.debug(
105
+ f"Could not determine cluster type, assuming remote: {e}",
106
+ )
107
+ # If we can't determine, assume remote for safety
108
+ return False
109
+
48
110
  def _parse_port_spec(self, port_spec):
49
111
  """
50
112
  Parse port specification.
@@ -92,6 +154,9 @@ class KubernetesClient(BaseClient):
92
154
  container_name = name or "main-container"
93
155
 
94
156
  # Container specification
157
+ if self.image_registry:
158
+ image = f"{self.image_registry}/{image}"
159
+
95
160
  container = client.V1Container(
96
161
  name=container_name,
97
162
  image=image,
@@ -562,9 +627,8 @@ class KubernetesClient(BaseClient):
562
627
  def _get_pod_node_ip(self, pod_name):
563
628
  """Get the IP of the node where the pod is running"""
564
629
 
565
- # Check if we are running in Colima, where pod runs in VM
566
- docker_host = os.getenv("DOCKER_HOST", "")
567
- if "colima" in docker_host.lower():
630
+ # Check if we are using a local Kubernetes cluster
631
+ if self._is_local_cluster():
568
632
  return "localhost"
569
633
 
570
634
  try:
@@ -601,3 +665,479 @@ class KubernetesClient(BaseClient):
601
665
  except Exception as e:
602
666
  logger.error(f"Failed to get pod node IP: {e}")
603
667
  return None
668
+
669
+ def _create_deployment_spec(
670
+ self,
671
+ image,
672
+ name,
673
+ ports=None,
674
+ volumes=None,
675
+ environment=None,
676
+ runtime_config=None,
677
+ replicas=1,
678
+ ):
679
+ """Create a Kubernetes Deployment specification."""
680
+ if runtime_config is None:
681
+ runtime_config = {}
682
+
683
+ container_name = name or "main-container"
684
+
685
+ # Use image registry if configured
686
+ if not self.image_registry:
687
+ full_image = image
688
+ else:
689
+ full_image = f"{self.image_registry}/{image}"
690
+
691
+ # Create container spec (reuse the existing pod spec logic)
692
+ container = client.V1Container(
693
+ name=container_name,
694
+ image=full_image,
695
+ image_pull_policy=runtime_config.get(
696
+ "image_pull_policy",
697
+ "IfNotPresent",
698
+ ),
699
+ )
700
+
701
+ # Configure ports
702
+ if ports:
703
+ container_ports = []
704
+ for port_spec in ports:
705
+ port_info = self._parse_port_spec(port_spec)
706
+ if port_info:
707
+ container_ports.append(
708
+ client.V1ContainerPort(
709
+ container_port=port_info["port"],
710
+ protocol=port_info["protocol"],
711
+ ),
712
+ )
713
+ if container_ports:
714
+ container.ports = container_ports
715
+
716
+ # Configure environment variables
717
+ if environment:
718
+ env_vars = []
719
+ for key, value in environment.items():
720
+ env_vars.append(client.V1EnvVar(name=key, value=str(value)))
721
+ container.env = env_vars
722
+
723
+ # Configure volume mounts and volumes
724
+ volume_mounts = []
725
+ pod_volumes = []
726
+ if volumes:
727
+ for volume_idx, (host_path, mount_info) in enumerate(
728
+ volumes.items(),
729
+ ):
730
+ if isinstance(mount_info, dict):
731
+ container_path = mount_info["bind"]
732
+ mode = mount_info.get("mode", "rw")
733
+ else:
734
+ container_path = mount_info
735
+ mode = "rw"
736
+ volume_name = f"vol-{volume_idx}"
737
+
738
+ # Create volume mount
739
+ volume_mounts.append(
740
+ client.V1VolumeMount(
741
+ name=volume_name,
742
+ mount_path=container_path,
743
+ read_only=(mode == "ro"),
744
+ ),
745
+ )
746
+ # Create host path volume
747
+ pod_volumes.append(
748
+ client.V1Volume(
749
+ name=volume_name,
750
+ host_path=client.V1HostPathVolumeSource(
751
+ path=host_path,
752
+ ),
753
+ ),
754
+ )
755
+
756
+ if volume_mounts:
757
+ container.volume_mounts = volume_mounts
758
+
759
+ # Apply runtime config to container
760
+ if "resources" in runtime_config:
761
+ container.resources = client.V1ResourceRequirements(
762
+ **runtime_config["resources"],
763
+ )
764
+
765
+ if "security_context" in runtime_config:
766
+ container.security_context = client.V1SecurityContext(
767
+ **runtime_config["security_context"],
768
+ )
769
+
770
+ # Pod template specification
771
+ pod_spec = client.V1PodSpec(
772
+ containers=[container],
773
+ restart_policy=runtime_config.get(
774
+ "restart_policy",
775
+ "Always",
776
+ ), # Deployments typically use Always
777
+ )
778
+
779
+ if pod_volumes:
780
+ pod_spec.volumes = pod_volumes
781
+
782
+ if "node_selector" in runtime_config:
783
+ pod_spec.node_selector = runtime_config["node_selector"]
784
+
785
+ if "tolerations" in runtime_config:
786
+ pod_spec.tolerations = runtime_config["tolerations"]
787
+
788
+ # Handle image pull secrets
789
+ image_pull_secrets = runtime_config.get("image_pull_secrets", [])
790
+ if image_pull_secrets:
791
+ secrets = []
792
+ for secret_name in image_pull_secrets:
793
+ secrets.append(client.V1LocalObjectReference(name=secret_name))
794
+ pod_spec.image_pull_secrets = secrets
795
+
796
+ # Pod template
797
+ pod_template = client.V1PodTemplateSpec(
798
+ metadata=client.V1ObjectMeta(
799
+ labels={
800
+ "app": name,
801
+ "created-by": "kubernetes-client",
802
+ },
803
+ ),
804
+ spec=pod_spec,
805
+ )
806
+
807
+ # Deployment specification
808
+ deployment_spec = client.V1DeploymentSpec(
809
+ replicas=replicas,
810
+ selector=client.V1LabelSelector(
811
+ match_labels={"app": name},
812
+ ),
813
+ template=pod_template,
814
+ )
815
+
816
+ return deployment_spec
817
+
818
+ def _create_loadbalancer_service(self, deployment_name, port_list):
819
+ """Create a LoadBalancer service for the deployment."""
820
+ try:
821
+ service_name = f"{deployment_name}-lb-service"
822
+ selector = {"app": deployment_name}
823
+
824
+ # Construct multi-port configuration
825
+ service_ports = []
826
+ for port_info in port_list:
827
+ port = port_info["port"]
828
+ protocol = port_info["protocol"]
829
+ service_ports.append(
830
+ client.V1ServicePort(
831
+ name=f"port-{port}",
832
+ port=port,
833
+ target_port=port,
834
+ protocol=protocol,
835
+ ),
836
+ )
837
+
838
+ service_spec = client.V1ServiceSpec(
839
+ selector=selector,
840
+ ports=service_ports,
841
+ type="LoadBalancer",
842
+ )
843
+
844
+ service = client.V1Service(
845
+ api_version="v1",
846
+ kind="Service",
847
+ metadata=client.V1ObjectMeta(
848
+ name=service_name,
849
+ namespace=self.namespace,
850
+ ),
851
+ spec=service_spec,
852
+ )
853
+
854
+ # Create the service
855
+ self.v1.create_namespaced_service(
856
+ namespace=self.namespace,
857
+ body=service,
858
+ )
859
+
860
+ logger.debug(
861
+ f"LoadBalancer service '{service_name}' created successfully",
862
+ )
863
+ return True, service_name
864
+ except Exception as e:
865
+ logger.error(
866
+ f"Failed to create LoadBalancer service for deployment "
867
+ f"{deployment_name}: "
868
+ f"{e}, {traceback.format_exc()}",
869
+ )
870
+ return False, None
871
+
872
+ def create_deployment(
873
+ self,
874
+ image,
875
+ name=None,
876
+ ports=None,
877
+ volumes=None,
878
+ environment=None,
879
+ runtime_config=None,
880
+ replicas=1,
881
+ create_service=True,
882
+ ) -> Tuple[str, list, str] | Tuple[None, None, None]:
883
+ """
884
+ Create a new Kubernetes Deployment with LoadBalancer service.
885
+
886
+ Args:
887
+ image: Container image name
888
+ name: Deployment name (auto-generated if None)
889
+ ports: List of ports to expose
890
+ volumes: Volume mounts dictionary
891
+ environment: Environment variables dictionary
892
+ runtime_config: Runtime configuration dictionary
893
+ replicas: Number of replicas for the deployment
894
+ create_service: Whether to create LoadBalancer service
895
+
896
+ Returns:
897
+ Tuple of (deployment_name, ports, load_balancer_ip)
898
+ """
899
+ if not name:
900
+ name = f"deploy-{hashlib.md5(image.encode()).hexdigest()[:8]}"
901
+
902
+ try:
903
+ # Create deployment specification
904
+ deployment_spec = self._create_deployment_spec(
905
+ image,
906
+ name,
907
+ ports,
908
+ volumes,
909
+ environment,
910
+ runtime_config,
911
+ replicas,
912
+ )
913
+
914
+ # Create deployment metadata
915
+ metadata = client.V1ObjectMeta(
916
+ name=name,
917
+ namespace=self.namespace,
918
+ labels={
919
+ "created-by": "kubernetes-client",
920
+ "app": name,
921
+ },
922
+ )
923
+
924
+ # Create deployment object
925
+ deployment = client.V1Deployment(
926
+ api_version="apps/v1",
927
+ kind="Deployment",
928
+ metadata=metadata,
929
+ spec=deployment_spec,
930
+ )
931
+
932
+ # Create the deployment
933
+ self.apps_v1.create_namespaced_deployment(
934
+ namespace=self.namespace,
935
+ body=deployment,
936
+ )
937
+
938
+ logger.debug(
939
+ f"Deployment '{name}' created successfully in namespace "
940
+ f"'{self.namespace}' with {replicas} replicas",
941
+ )
942
+
943
+ service_info = None
944
+ load_balancer_ip = None
945
+
946
+ # Create LoadBalancer service if requested and ports are specified
947
+ if create_service and ports:
948
+ parsed_ports = []
949
+ for port_spec in ports:
950
+ port_info = self._parse_port_spec(port_spec)
951
+ if port_info:
952
+ parsed_ports.append(port_info)
953
+
954
+ if parsed_ports:
955
+ (
956
+ service_created,
957
+ service_name,
958
+ ) = self._create_loadbalancer_service(
959
+ name,
960
+ parsed_ports,
961
+ )
962
+ if service_created:
963
+ service_info = {
964
+ "name": service_name,
965
+ "type": "LoadBalancer",
966
+ "ports": [p["port"] for p in parsed_ports],
967
+ }
968
+ # Wait a bit and try to get LoadBalancer IP
969
+ time.sleep(2)
970
+ load_balancer_ip = self._get_loadbalancer_ip(
971
+ service_name,
972
+ )
973
+
974
+ # Wait for deployment to be ready
975
+ if not self.wait_for_deployment_ready(name, timeout=120):
976
+ logger.warning(f"Deployment '{name}' may not be fully ready")
977
+
978
+ logger.debug(
979
+ f"Deployment '{name}' created successfully with service: "
980
+ f"{service_info}",
981
+ )
982
+
983
+ return (
984
+ name,
985
+ service_info["ports"] if service_info else [],
986
+ load_balancer_ip or "",
987
+ )
988
+
989
+ except Exception as e:
990
+ logger.error(
991
+ f"Failed to create deployment: {e}, {traceback.format_exc()}",
992
+ )
993
+ return None, None, None
994
+
995
+ def wait_for_deployment_ready(self, deployment_name, timeout=300):
996
+ """Wait for a deployment to be ready."""
997
+ start_time = time.time()
998
+ while time.time() - start_time < timeout:
999
+ try:
1000
+ deployment = self.apps_v1.read_namespaced_deployment(
1001
+ name=deployment_name,
1002
+ namespace=self.namespace,
1003
+ )
1004
+
1005
+ # Check if deployment is ready
1006
+ if (
1007
+ deployment.status.ready_replicas
1008
+ and deployment.status.ready_replicas
1009
+ == deployment.spec.replicas
1010
+ ):
1011
+ return True
1012
+
1013
+ time.sleep(3)
1014
+ except ApiException as e:
1015
+ if e.status == 404:
1016
+ return False
1017
+ time.sleep(3)
1018
+ return False
1019
+
1020
+ def _get_loadbalancer_ip(self, service_name, timeout=30):
1021
+ """Get LoadBalancer external IP address."""
1022
+ start_time = time.time()
1023
+ while time.time() - start_time < timeout:
1024
+ try:
1025
+ service = self.v1.read_namespaced_service(
1026
+ name=service_name,
1027
+ namespace=self.namespace,
1028
+ )
1029
+
1030
+ if (
1031
+ service.status.load_balancer
1032
+ and service.status.load_balancer.ingress
1033
+ ):
1034
+ ingress = service.status.load_balancer.ingress[0]
1035
+ # Return IP or hostname
1036
+ return ingress.ip or ingress.hostname
1037
+
1038
+ time.sleep(2)
1039
+ except Exception as e:
1040
+ logger.debug(f"Waiting for LoadBalancer IP: {e}")
1041
+ time.sleep(2)
1042
+
1043
+ logger.debug(
1044
+ f"LoadBalancer IP not available for service {service_name}",
1045
+ )
1046
+ return None
1047
+
1048
+ def remove_deployment(self, deployment_name, remove_service=True):
1049
+ """Remove a Kubernetes Deployment and optionally its service."""
1050
+ try:
1051
+ # Remove associated LoadBalancer service first if requested
1052
+ if remove_service:
1053
+ service_name = f"{deployment_name}-lb-service"
1054
+ try:
1055
+ self.v1.delete_namespaced_service(
1056
+ name=service_name,
1057
+ namespace=self.namespace,
1058
+ )
1059
+ logger.debug(
1060
+ f"Removed LoadBalancer service {service_name}",
1061
+ )
1062
+ except ApiException as e:
1063
+ if e.status != 404:
1064
+ logger.warning(
1065
+ f"Failed to remove service {service_name}: {e}",
1066
+ )
1067
+
1068
+ # Remove the deployment
1069
+ self.apps_v1.delete_namespaced_deployment(
1070
+ name=deployment_name,
1071
+ namespace=self.namespace,
1072
+ body=client.V1DeleteOptions(
1073
+ propagation_policy="Foreground",
1074
+ ),
1075
+ )
1076
+
1077
+ logger.debug(
1078
+ f"Deployment '{deployment_name}' removed successfully",
1079
+ )
1080
+ return True
1081
+
1082
+ except ApiException as e:
1083
+ if e.status == 404:
1084
+ logger.warning(f"Deployment '{deployment_name}' not found")
1085
+ return True
1086
+ logger.error(f"Failed to remove deployment: {e.reason}")
1087
+ return False
1088
+ except Exception as e:
1089
+ logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
1090
+ return False
1091
+
1092
+ def get_deployment_status(self, deployment_name):
1093
+ """Get the current status of the specified deployment."""
1094
+ try:
1095
+ deployment = self.apps_v1.read_namespaced_deployment(
1096
+ name=deployment_name,
1097
+ namespace=self.namespace,
1098
+ )
1099
+
1100
+ return {
1101
+ "name": deployment_name,
1102
+ "replicas": deployment.spec.replicas,
1103
+ "ready_replicas": deployment.status.ready_replicas or 0,
1104
+ "available_replicas": deployment.status.available_replicas
1105
+ or 0,
1106
+ "unavailable_replicas": deployment.status.unavailable_replicas
1107
+ or 0,
1108
+ "conditions": [
1109
+ {
1110
+ "type": condition.type,
1111
+ "status": condition.status,
1112
+ "reason": condition.reason,
1113
+ "message": condition.message,
1114
+ }
1115
+ for condition in (deployment.status.conditions or [])
1116
+ ],
1117
+ }
1118
+ except ApiException as e:
1119
+ if e.status == 404:
1120
+ logger.warning(f"Deployment '{deployment_name}' not found")
1121
+ else:
1122
+ logger.error(f"Failed to get deployment status: {e.reason}")
1123
+ return None
1124
+ except Exception as e:
1125
+ logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
1126
+ return None
1127
+
1128
+ def list_deployments(self, label_selector=None):
1129
+ """List deployments in the namespace."""
1130
+ try:
1131
+ deployments = self.apps_v1.list_namespaced_deployment(
1132
+ namespace=self.namespace,
1133
+ label_selector=label_selector,
1134
+ )
1135
+ return [
1136
+ deployment.metadata.name for deployment in deployments.items
1137
+ ]
1138
+ except ApiException as e:
1139
+ logger.error(f"Failed to list deployments: {e.reason}")
1140
+ return []
1141
+ except Exception as e:
1142
+ logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
1143
+ return []
@@ -7,3 +7,15 @@ from .services import (
7
7
  )
8
8
  from .deployers import DeployManager, LocalDeployManager
9
9
  from .runner import Runner
10
+ from .app import AgentApp
11
+
12
+ __all__ = [
13
+ "Service",
14
+ "SandboxService",
15
+ "MemoryService",
16
+ "SessionHistoryService",
17
+ "DeployManager",
18
+ "LocalDeployManager",
19
+ "Runner",
20
+ "AgentApp",
21
+ ]