agentscope-runtime 1.0.4a1__py3-none-any.whl → 1.0.5__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 (79) hide show
  1. agentscope_runtime/adapters/agentscope/stream.py +2 -8
  2. agentscope_runtime/adapters/langgraph/stream.py +120 -70
  3. agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
  4. agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
  5. agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
  6. agentscope_runtime/adapters/utils.py +6 -0
  7. agentscope_runtime/cli/commands/deploy.py +836 -1
  8. agentscope_runtime/cli/commands/stop.py +16 -0
  9. agentscope_runtime/common/container_clients/__init__.py +52 -0
  10. agentscope_runtime/common/container_clients/agentrun_client.py +6 -4
  11. agentscope_runtime/common/container_clients/boxlite_client.py +442 -0
  12. agentscope_runtime/common/container_clients/docker_client.py +0 -20
  13. agentscope_runtime/common/container_clients/fc_client.py +6 -4
  14. agentscope_runtime/common/container_clients/gvisor_client.py +38 -0
  15. agentscope_runtime/common/container_clients/knative_client.py +467 -0
  16. agentscope_runtime/common/utils/deprecation.py +164 -0
  17. agentscope_runtime/engine/__init__.py +4 -0
  18. agentscope_runtime/engine/app/agent_app.py +16 -4
  19. agentscope_runtime/engine/constant.py +1 -0
  20. agentscope_runtime/engine/deployers/__init__.py +34 -11
  21. agentscope_runtime/engine/deployers/adapter/__init__.py +8 -0
  22. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +26 -51
  23. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +23 -13
  24. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +4 -201
  25. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +152 -25
  26. agentscope_runtime/engine/deployers/adapter/agui/__init__.py +8 -0
  27. agentscope_runtime/engine/deployers/adapter/agui/agui_adapter_utils.py +652 -0
  28. agentscope_runtime/engine/deployers/adapter/agui/agui_protocol_adapter.py +225 -0
  29. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  30. agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
  31. agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
  32. agentscope_runtime/engine/deployers/pai_deployer.py +2335 -0
  33. agentscope_runtime/engine/deployers/utils/net_utils.py +37 -0
  34. agentscope_runtime/engine/deployers/utils/oss_utils.py +38 -0
  35. agentscope_runtime/engine/deployers/utils/package.py +46 -42
  36. agentscope_runtime/engine/helpers/agent_api_client.py +372 -0
  37. agentscope_runtime/engine/runner.py +13 -0
  38. agentscope_runtime/engine/schemas/agent_schemas.py +9 -3
  39. agentscope_runtime/engine/services/agent_state/__init__.py +7 -0
  40. agentscope_runtime/engine/services/memory/__init__.py +7 -0
  41. agentscope_runtime/engine/services/memory/redis_memory_service.py +15 -16
  42. agentscope_runtime/engine/services/session_history/__init__.py +7 -0
  43. agentscope_runtime/engine/tracing/local_logging_handler.py +2 -3
  44. agentscope_runtime/engine/tracing/wrapper.py +18 -4
  45. agentscope_runtime/sandbox/__init__.py +14 -6
  46. agentscope_runtime/sandbox/box/base/__init__.py +2 -2
  47. agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
  48. agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
  50. agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
  51. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
  52. agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
  53. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
  54. agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
  55. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
  56. agentscope_runtime/sandbox/box/sandbox.py +102 -65
  57. agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
  58. agentscope_runtime/sandbox/client/__init__.py +6 -1
  59. agentscope_runtime/sandbox/client/async_http_client.py +339 -0
  60. agentscope_runtime/sandbox/client/base.py +74 -0
  61. agentscope_runtime/sandbox/client/http_client.py +108 -329
  62. agentscope_runtime/sandbox/enums.py +7 -0
  63. agentscope_runtime/sandbox/manager/sandbox_manager.py +275 -29
  64. agentscope_runtime/sandbox/manager/server/app.py +7 -1
  65. agentscope_runtime/sandbox/manager/server/config.py +3 -1
  66. agentscope_runtime/sandbox/model/manager_config.py +11 -9
  67. agentscope_runtime/tools/modelstudio_memory/__init__.py +106 -0
  68. agentscope_runtime/tools/modelstudio_memory/base.py +220 -0
  69. agentscope_runtime/tools/modelstudio_memory/config.py +86 -0
  70. agentscope_runtime/tools/modelstudio_memory/core.py +594 -0
  71. agentscope_runtime/tools/modelstudio_memory/exceptions.py +60 -0
  72. agentscope_runtime/tools/modelstudio_memory/schemas.py +253 -0
  73. agentscope_runtime/version.py +1 -1
  74. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/METADATA +186 -73
  75. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/RECORD +79 -55
  76. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/WHEEL +0 -0
  77. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/entry_points.txt +0 -0
  78. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/licenses/LICENSE +0 -0
  79. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,13 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """agentscope deploy command - Deploy agents to various platforms."""
3
3
  # pylint: disable=too-many-statements, too-many-branches
4
+ # pylint: disable=too-many-nested-blocks
4
5
 
5
6
  import asyncio
6
7
  import json
7
8
  import os
8
9
  import sys
10
+ from typing import Optional
9
11
 
10
12
  import click
11
13
  import yaml
@@ -56,6 +58,26 @@ try:
56
58
  except ImportError:
57
59
  K8S_AVAILABLE = False
58
60
 
61
+ try:
62
+ from agentscope_runtime.engine.deployers.knative_deployer import (
63
+ KnativeDeployManager,
64
+ )
65
+
66
+ KNATIVE_AVAILABLE = True
67
+ except ImportError:
68
+ KNATIVE_AVAILABLE = False
69
+
70
+ try:
71
+ from agentscope_runtime.engine.deployers.pai_deployer import (
72
+ PAI_AVAILABLE,
73
+ PAIDeployConfig,
74
+ PAIDeployManager,
75
+ )
76
+ except ImportError:
77
+ PAI_AVAILABLE = False
78
+ PAIDeployConfig = None
79
+ PAIDeployManager = None
80
+
59
81
 
60
82
  def _validate_source(source: str) -> tuple[str, str]:
61
83
  """
@@ -81,7 +103,10 @@ def _validate_source(source: str) -> tuple[str, str]:
81
103
  raise ValueError(f"Source must be a file or directory: {abs_source}")
82
104
 
83
105
 
84
- def _find_entrypoint(project_dir: str, entrypoint: str = None) -> str:
106
+ def _find_entrypoint(
107
+ project_dir: str,
108
+ entrypoint: Optional[str] = None,
109
+ ) -> str:
85
110
  """
86
111
  Find or validate entrypoint file in project directory.
87
112
 
@@ -176,6 +201,33 @@ def _merge_config(config_dict: dict, cli_params: dict) -> dict:
176
201
  return merged
177
202
 
178
203
 
204
+ def _parse_tags(tag_tuples: tuple) -> dict:
205
+ """
206
+ Parse tags from --tag options.
207
+
208
+ Args:
209
+ tag_tuples: Tuple of KEY=VALUE strings from --tag options
210
+
211
+ Returns:
212
+ Dictionary of tags
213
+
214
+ Raises:
215
+ ValueError: If tag format is invalid
216
+ """
217
+ tags = {}
218
+
219
+ for tag_pair in tag_tuples:
220
+ if "=" not in tag_pair:
221
+ raise ValueError(
222
+ f"Invalid tag format: '{tag_pair}'. Use KEY=VALUE format",
223
+ )
224
+
225
+ key, value = tag_pair.split("=", 1)
226
+ tags[key.strip()] = value.strip()
227
+
228
+ return tags
229
+
230
+
179
231
  def _parse_environment(env_tuples: tuple, env_file: str = None) -> dict:
180
232
  """
181
233
  Parse environment variables from --env options and --env-file.
@@ -245,8 +297,10 @@ def deploy():
245
297
  \b
246
298
  - modelstudio: Alibaba Cloud ModelStudio
247
299
  - agentrun: Alibaba Cloud AgentRun
300
+ - pai: Alibaba Cloud PAI (Platform for AI)
248
301
  - k8s: Kubernetes/ACK
249
302
  - local: Local deployment (detached mode)
303
+ - Knative: Knative/ACK Knative
250
304
 
251
305
  Use 'agentscope deploy <platform> --help' for platform-specific options.
252
306
  """
@@ -701,6 +755,426 @@ def agentrun(
701
755
  sys.exit(1)
702
756
 
703
757
 
758
+ @deploy.command()
759
+ @click.argument("source", required=False, default=None)
760
+ @click.option(
761
+ "--config",
762
+ "-c",
763
+ type=click.Path(exists=True),
764
+ help="Path to deployment config file (.yaml or .yml). "
765
+ "See pai_deploy_config.yaml for example.",
766
+ )
767
+ @click.option("--name", help="Service name (required)", default=None)
768
+ @click.option(
769
+ "--workspace-id",
770
+ help="PAI workspace ID (or PAI_WORKSPACE_ID env var)",
771
+ default=None,
772
+ )
773
+ @click.option(
774
+ "--region",
775
+ help="Region ID (e.g., cn-hangzhou)",
776
+ default=None,
777
+ )
778
+ @click.option(
779
+ "--entrypoint",
780
+ help="Entrypoint file name (default: app.py, agent.py, or main.py)",
781
+ default=None,
782
+ )
783
+ @click.option(
784
+ "--oss-path",
785
+ help="OSS work directory (e.g., oss://bucket/path/)",
786
+ default=None,
787
+ )
788
+ @click.option(
789
+ "--instance-type",
790
+ help="Instance type for public resource (e.g., ecs.c6.large)",
791
+ default=None,
792
+ )
793
+ @click.option(
794
+ "--instance-count",
795
+ help="Number of instances",
796
+ type=int,
797
+ default=None,
798
+ )
799
+ @click.option(
800
+ "--resource-id",
801
+ help="EAS resource group ID (for resource mode)",
802
+ default=None,
803
+ )
804
+ @click.option(
805
+ "--quota-id",
806
+ help="PAI quota ID (for quota mode)",
807
+ default=None,
808
+ )
809
+ @click.option("--cpu", help="CPU cores", type=int, default=None)
810
+ @click.option("--memory", help="Memory in MB", type=int, default=None)
811
+ @click.option("--service-group", help="Service group name", default=None)
812
+ @click.option(
813
+ "--resource-type",
814
+ type=click.Choice(["public", "resource", "quota"]),
815
+ help="Resource type: public (instance), resource (EAS group), quota",
816
+ default=None,
817
+ )
818
+ @click.option(
819
+ "--vpc-id",
820
+ help="VPC ID for network configuration",
821
+ default=None,
822
+ )
823
+ @click.option(
824
+ "--vswitch-id",
825
+ help="VSwitch ID for network configuration",
826
+ default=None,
827
+ )
828
+ @click.option(
829
+ "--security-group-id",
830
+ help="Security group ID for network configuration",
831
+ default=None,
832
+ )
833
+ @click.option("--ram-role-arn", help="RAM role ARN", default=None)
834
+ @click.option(
835
+ "--enable-trace/--no-trace",
836
+ help="Enable/disable tracing",
837
+ default=None,
838
+ )
839
+ @click.option(
840
+ "--wait/--no-wait",
841
+ help="Wait for deployment to complete",
842
+ default=None,
843
+ )
844
+ @click.option(
845
+ "--timeout",
846
+ help="Deployment timeout in seconds",
847
+ type=int,
848
+ default=None,
849
+ )
850
+ @click.option(
851
+ "--auto-approve/--no-auto-approve",
852
+ help="Auto approve the deployment",
853
+ default=None,
854
+ )
855
+ @click.option(
856
+ "--env",
857
+ "-E",
858
+ multiple=True,
859
+ help="Environment variable in KEY=VALUE format (can be repeated)",
860
+ )
861
+ @click.option(
862
+ "--env-file",
863
+ type=click.Path(exists=True),
864
+ help="Path to .env file with environment variables",
865
+ )
866
+ @click.option(
867
+ "--tag",
868
+ "-T",
869
+ multiple=True,
870
+ help="Tag in KEY=VALUE format (can be repeated)",
871
+ )
872
+ def pai(
873
+ source: str,
874
+ config: str,
875
+ name: str,
876
+ workspace_id: str,
877
+ region: str,
878
+ entrypoint: str,
879
+ oss_path: str,
880
+ instance_type: str,
881
+ instance_count: int,
882
+ resource_id: str,
883
+ quota_id: str,
884
+ cpu: int,
885
+ memory: int,
886
+ service_group: str,
887
+ resource_type: str,
888
+ vpc_id: str,
889
+ vswitch_id: str,
890
+ security_group_id: str,
891
+ ram_role_arn: str,
892
+ enable_trace: bool,
893
+ wait: bool,
894
+ timeout: int,
895
+ auto_approve: bool,
896
+ env: tuple,
897
+ env_file: str,
898
+ tag: tuple,
899
+ ):
900
+ """
901
+ Deploy to Alibaba Cloud PAI (Platform for AI).
902
+
903
+ \b
904
+ Usage:
905
+ # Using config file (recommended)
906
+ agentscope deploy pai --config pai_deploy_config.yaml
907
+
908
+ # Using config file with CLI overrides
909
+ agentscope deploy pai --config pai_deploy_config.yaml --name new-name
910
+
911
+ # Using CLI only
912
+ agentscope deploy pai ./my_agent --name my-service --workspace-id 12345
913
+
914
+ \b
915
+ Required:
916
+ - Service name (--name or spec.name in config)
917
+ - Source directory (SOURCE argument or spec.code.source_dir in config)
918
+ - Workspace ID (--workspace-id, context.workspace_id, or env)
919
+
920
+ \b
921
+ Environment variables:
922
+ - ALIBABA_CLOUD_ACCESS_KEY_ID
923
+ - ALIBABA_CLOUD_ACCESS_KEY_SECRET
924
+ - PAI_WORKSPACE_ID (optional if --workspace-id provided)
925
+ - REGION_ID or ALIBABA_CLOUD_REGION_ID (optional)
926
+ """
927
+ if not PAI_AVAILABLE:
928
+ echo_error("PAI deployer is not available")
929
+ echo_info(
930
+ "Please install required dependencies via "
931
+ "pip install 'agentscope-runtime[ext]'",
932
+ )
933
+ sys.exit(1)
934
+
935
+ try:
936
+ # Step 1: Build configuration from config file or defaults
937
+ if config:
938
+ echo_info(f"Loading configuration from {config}...")
939
+ deploy_config = PAIDeployConfig.from_yaml(config)
940
+ else:
941
+ deploy_config = PAIDeployConfig()
942
+
943
+ # Step 2: Parse CLI environment variables and tags
944
+ cli_env = _parse_environment(env, env_file)
945
+ cli_tags = _parse_tags(tag)
946
+
947
+ # Step 3: Resolve source path
948
+ resolved_source = None
949
+ if source:
950
+ abs_source, source_type = _validate_source(source)
951
+ if source_type == "file":
952
+ # For single file: use parent directory as project_dir
953
+ resolved_source = os.path.dirname(abs_source)
954
+ if not entrypoint:
955
+ entrypoint = os.path.basename(abs_source)
956
+ else:
957
+ resolved_source = abs_source
958
+
959
+ # Step 4: Merge CLI parameters (CLI takes precedence)
960
+ deploy_config = deploy_config.merge_cli(
961
+ source=resolved_source,
962
+ name=name,
963
+ entrypoint=entrypoint,
964
+ workspace_id=workspace_id,
965
+ region=region,
966
+ oss_path=oss_path,
967
+ instance_type=instance_type,
968
+ instance_count=instance_count,
969
+ resource_id=resource_id,
970
+ quota_id=quota_id,
971
+ cpu=cpu,
972
+ memory=memory,
973
+ service_group=service_group,
974
+ resource_type=resource_type,
975
+ vpc_id=vpc_id,
976
+ vswitch_id=vswitch_id,
977
+ security_group_id=security_group_id,
978
+ ram_role_arn=ram_role_arn,
979
+ enable_trace=enable_trace,
980
+ wait=wait,
981
+ timeout=timeout,
982
+ auto_approve=auto_approve,
983
+ environment=cli_env if cli_env else None,
984
+ tags=cli_tags if cli_tags else None,
985
+ )
986
+
987
+ # Step 5: Resolve source_dir to absolute path
988
+ if deploy_config.spec.code.source_dir:
989
+ source_dir = deploy_config.spec.code.source_dir
990
+ if not os.path.isabs(source_dir):
991
+ # If config file provided, resolve relative to config file dir
992
+ if config:
993
+ base_dir = os.path.dirname(os.path.abspath(config))
994
+ source_dir = os.path.join(base_dir, source_dir)
995
+ else:
996
+ source_dir = os.path.abspath(source_dir)
997
+ # Update with resolved path
998
+ deploy_config.spec.code.source_dir = source_dir
999
+
1000
+ # Step 6: Find entrypoint if not specified
1001
+ if (
1002
+ deploy_config.spec.code.source_dir
1003
+ and not deploy_config.spec.code.entrypoint
1004
+ ):
1005
+ entry_script = _find_entrypoint(
1006
+ deploy_config.spec.code.source_dir,
1007
+ )
1008
+ deploy_config.spec.code.entrypoint = entry_script
1009
+
1010
+ # Step 7: Validate configuration
1011
+ try:
1012
+ deploy_config.validate_for_deploy()
1013
+ except ValueError as e:
1014
+ echo_error(str(e))
1015
+ sys.exit(1)
1016
+
1017
+ # Step 8: Create deployer
1018
+ # Use resolved OSS work dir (spec.storage -> context.storage fallback)
1019
+ resolved_oss_path = deploy_config.resolve_oss_work_dir()
1020
+ deployer = PAIDeployManager(
1021
+ workspace_id=deploy_config.context.workspace_id,
1022
+ region_id=deploy_config.context.region,
1023
+ oss_path=resolved_oss_path,
1024
+ )
1025
+
1026
+ # Validate workspace_id (may come from env var in deployer)
1027
+ if not deployer.workspace_id:
1028
+ echo_error(
1029
+ "PAI workspace ID is required. Set PAI_WORKSPACE_ID "
1030
+ "environment variable, use --workspace-id, or set "
1031
+ "context.workspace_id in config file.",
1032
+ )
1033
+ sys.exit(1)
1034
+
1035
+ # Step 9: Display deployment info
1036
+ service_name = deploy_config.spec.name
1037
+ resource_type = deploy_config.resolve_resource_type()
1038
+
1039
+ echo_info(f"Service Name: {service_name}")
1040
+ echo_info(f"Workspace ID: {deployer.workspace_id}")
1041
+ echo_info(f"Region: {deployer.region_id}")
1042
+ echo_info(f"Resource Type: {resource_type}")
1043
+ echo_info(f"Source: {deploy_config.spec.code.source_dir}")
1044
+ if deploy_config.spec.code.entrypoint:
1045
+ echo_info(f"Entrypoint: {deploy_config.spec.code.entrypoint}")
1046
+ if deployer.oss_path:
1047
+ echo_info(f"OSS Path: {deployer.oss_path}")
1048
+ if deploy_config.spec.tags:
1049
+ echo_info(f"Tags: {deploy_config.spec.tags}")
1050
+
1051
+ # Step 10: Deploy
1052
+ echo_info(f"Deploying to PAI as service '{service_name}'...")
1053
+
1054
+ deploy_kwargs = deploy_config.to_deployer_kwargs()
1055
+ # Add deploy_method to indicate this is a CLI deployment
1056
+ deploy_kwargs["deploy_method"] = "cli"
1057
+ result = asyncio.run(deployer.deploy(**deploy_kwargs))
1058
+
1059
+ # Step 11: Display results
1060
+ deploy_id = result.get("deploy_id")
1061
+ flow_id = result.get("flow_id")
1062
+ snapshot_id = result.get("snapshot_id")
1063
+ console_url = result.get("url")
1064
+ status = result.get("status")
1065
+
1066
+ echo_success("Deployment created!")
1067
+ echo_info(f"Deployment ID: {deploy_id}")
1068
+ echo_info(f"Project ID: {flow_id}")
1069
+ echo_info(f"Snapshot ID: {snapshot_id}")
1070
+ echo_info(f"Service Name: {service_name}")
1071
+ echo_info(f"Status: {status}")
1072
+ echo_info(f"Deployment Console URL: {console_url}")
1073
+
1074
+ # Step 12: Handle approval flow for auto_approve=False
1075
+ if not deploy_config.auto_approve:
1076
+ # Interactive approval flow
1077
+ echo_info("\n" + "=" * 60)
1078
+ echo_warning(
1079
+ "Deployment created but waiting for approval.",
1080
+ )
1081
+ echo_info(
1082
+ f"View deployment details at:\n {console_url}",
1083
+ )
1084
+ echo_info("=" * 60)
1085
+
1086
+ # Check if we're in an interactive terminal
1087
+ if sys.stdin.isatty():
1088
+ echo_info("\nWhat would you like to do?")
1089
+ echo_info(" [A]pprove - Approve and start deployment")
1090
+ echo_info(" [C]ancel - Cancel this deployment")
1091
+ echo_info(" [S]kip - Skip (approve later in console)")
1092
+
1093
+ choice = click.prompt(
1094
+ "\nYour choice(case insensitive)",
1095
+ type=click.Choice(
1096
+ ["A", "C", "S"],
1097
+ case_sensitive=False,
1098
+ ),
1099
+ default="S",
1100
+ )
1101
+ choice = choice.upper()
1102
+
1103
+ if choice == "A":
1104
+ echo_info("\nApproving deployment...")
1105
+ try:
1106
+ # Wait for deployment to reach approval stage
1107
+ asyncio.run(
1108
+ deployer.wait_for_approval_stage(deploy_id),
1109
+ )
1110
+ # Approve the deployment
1111
+ echo_info("Deployment approved by CLI.")
1112
+ asyncio.run(
1113
+ deployer.approve_deployment(
1114
+ deploy_id,
1115
+ wait=deploy_config.wait,
1116
+ timeout=deploy_config.timeout,
1117
+ ),
1118
+ )
1119
+ echo_success("Deployment completed!")
1120
+
1121
+ # Get updated service info
1122
+ if deploy_config.wait:
1123
+ service = asyncio.run(
1124
+ deployer.get_service(service_name),
1125
+ )
1126
+ if service and service.internet_endpoint:
1127
+ echo_info(
1128
+ f"Service Endpoint: "
1129
+ f"{service.internet_endpoint}",
1130
+ )
1131
+ echo_info(
1132
+ f"\nDeployment is running. Use "
1133
+ f"'agentscope stop {deploy_id}' to stop it.",
1134
+ )
1135
+ except Exception as e:
1136
+ echo_error(f"Failed to approve deployment: {e}")
1137
+ sys.exit(1)
1138
+
1139
+ elif choice == "C":
1140
+ echo_info("\nCancelling deployment...")
1141
+ try:
1142
+ asyncio.run(
1143
+ deployer.wait_for_approval_stage(deploy_id),
1144
+ )
1145
+ asyncio.run(deployer.cancel_deployment(deploy_id))
1146
+ echo_warning("Deployment cancelled.")
1147
+ except Exception as e:
1148
+ echo_error(f"Failed to cancel deployment: {e}")
1149
+ sys.exit(1)
1150
+
1151
+ else: # choice == "S"
1152
+ echo_info(
1153
+ "\nSkipped. Please approve or cancel the deployment "
1154
+ "in the PAI console.",
1155
+ )
1156
+ else:
1157
+ # Non-interactive mode
1158
+ echo_warning(
1159
+ "\nNon-interactive mode: Please approve or cancel the "
1160
+ "deployment in the PAI console.",
1161
+ )
1162
+
1163
+ elif deploy_config.wait:
1164
+ echo_success("Deployment completed successfully!")
1165
+ echo_info(
1166
+ f"\nDeployment is running. Use 'agentscope stop "
1167
+ f"{deploy_id}' to stop it.",
1168
+ )
1169
+
1170
+ except Exception as e:
1171
+ echo_error(f"Deployment failed: {e}")
1172
+ import traceback
1173
+
1174
+ echo_error(traceback.format_exc())
1175
+ sys.exit(1)
1176
+
1177
+
704
1178
  @deploy.command()
705
1179
  @click.argument("source", required=True)
706
1180
  @click.option("--name", help="Deployment name", default=None)
@@ -1070,5 +1544,366 @@ def k8s(
1070
1544
  sys.exit(1)
1071
1545
 
1072
1546
 
1547
+ @deploy.command()
1548
+ @click.argument("source", required=True)
1549
+ @click.option("--name", help="Deployment name", default=None)
1550
+ @click.option(
1551
+ "--namespace",
1552
+ help="Kubernetes namespace",
1553
+ default="agentscope-runtime",
1554
+ )
1555
+ @click.option(
1556
+ "--kube-config-path",
1557
+ "-c",
1558
+ type=click.Path(exists=True),
1559
+ help="Path to knative service config file (.json, .yaml, or .yml)",
1560
+ )
1561
+ @click.option(
1562
+ "--port",
1563
+ help="Container port",
1564
+ type=int,
1565
+ default=8080,
1566
+ )
1567
+ @click.option(
1568
+ "--image-name",
1569
+ help="Docker image name",
1570
+ default="agent_app",
1571
+ )
1572
+ @click.option(
1573
+ "--image-tag",
1574
+ help="Docker image tag",
1575
+ default="linux-amd64",
1576
+ )
1577
+ @click.option(
1578
+ "--registry-url",
1579
+ help="Remote registry url",
1580
+ default="localhost",
1581
+ )
1582
+ @click.option(
1583
+ "--registry-namespace",
1584
+ help="Remote registry namespace",
1585
+ default="agentscope-runtime",
1586
+ )
1587
+ @click.option(
1588
+ "--push",
1589
+ is_flag=True,
1590
+ help="Push image to registry",
1591
+ )
1592
+ @click.option(
1593
+ "--entrypoint",
1594
+ "-e",
1595
+ help="Entrypoint file name for directory sources (e.g., 'app.py', "
1596
+ "'main.py')",
1597
+ default=None,
1598
+ )
1599
+ @click.option(
1600
+ "--env",
1601
+ "-E",
1602
+ multiple=True,
1603
+ help="Environment variable in KEY=VALUE format (can be repeated)",
1604
+ )
1605
+ @click.option(
1606
+ "--env-file",
1607
+ type=click.Path(exists=True),
1608
+ help="Path to .env file with environment variables",
1609
+ )
1610
+ @click.option(
1611
+ "--config",
1612
+ "-c",
1613
+ type=click.Path(exists=True),
1614
+ help="Path to knative service config file (.json, .yaml, or .yml)",
1615
+ )
1616
+ @click.option(
1617
+ "--base-image",
1618
+ help="Base Docker image",
1619
+ default="python:3.10-slim-bookworm",
1620
+ )
1621
+ @click.option(
1622
+ "--requirements",
1623
+ help="Python requirements (comma-separated or file path)",
1624
+ default=None,
1625
+ )
1626
+ @click.option(
1627
+ "--cpu-request",
1628
+ help="CPU resource request (e.g., '200m', '1')",
1629
+ default="200m",
1630
+ )
1631
+ @click.option(
1632
+ "--cpu-limit",
1633
+ help="CPU resource limit (e.g., '1000m', '2')",
1634
+ default="1000m",
1635
+ )
1636
+ @click.option(
1637
+ "--memory-request",
1638
+ help="Memory resource request (e.g., '512Mi', '1Gi')",
1639
+ default="512Mi",
1640
+ )
1641
+ @click.option(
1642
+ "--memory-limit",
1643
+ help="Memory resource limit (e.g., '2Gi', '4Gi')",
1644
+ default="2Gi",
1645
+ )
1646
+ @click.option(
1647
+ "--image-pull-policy",
1648
+ help="Image pull policy",
1649
+ type=click.Choice(["Always", "IfNotPresent", "Never"]),
1650
+ default="IfNotPresent",
1651
+ )
1652
+ @click.option(
1653
+ "--deploy-timeout",
1654
+ help="Deployment timeout in seconds",
1655
+ type=int,
1656
+ default=300,
1657
+ )
1658
+ @click.option(
1659
+ "--health-check",
1660
+ is_flag=True,
1661
+ help="Enable/disable health check",
1662
+ )
1663
+ @click.option(
1664
+ "--platform",
1665
+ help="Target platform (e.g., 'linux/amd64', 'linux/arm64')",
1666
+ default="linux/amd64",
1667
+ )
1668
+ @click.option(
1669
+ "--pypi-mirror",
1670
+ help="PyPI mirror URL for pip package installation (e.g., "
1671
+ "https://pypi.tuna.tsinghua.edu.cn/simple). If not specified, "
1672
+ "uses pip default.",
1673
+ default=None,
1674
+ )
1675
+ def knative(
1676
+ source: str,
1677
+ name: str,
1678
+ namespace: str,
1679
+ kube_config_path: str,
1680
+ port: int,
1681
+ image_name: str,
1682
+ image_tag: str,
1683
+ registry_url: str,
1684
+ registry_namespace: str,
1685
+ push: bool,
1686
+ entrypoint: str,
1687
+ env: tuple,
1688
+ env_file: str,
1689
+ config: str,
1690
+ base_image: str,
1691
+ requirements: str,
1692
+ cpu_request: str,
1693
+ cpu_limit: str,
1694
+ memory_request: str,
1695
+ memory_limit: str,
1696
+ image_pull_policy: str,
1697
+ deploy_timeout: int,
1698
+ health_check: bool,
1699
+ platform: str,
1700
+ pypi_mirror: str,
1701
+ ):
1702
+ """
1703
+ Deploy to Knative/ACK Knative.
1704
+
1705
+ SOURCE can be a Python file or project directory containing an agent.
1706
+
1707
+ This will build a Docker image and deploy it to your Knative cluster.
1708
+ """
1709
+ if not KNATIVE_AVAILABLE:
1710
+ echo_error("Knative deployer is not available")
1711
+ echo_info("Please ensure Knative are available")
1712
+ sys.exit(1)
1713
+
1714
+ try:
1715
+ echo_info(f"Preparing deployment from {source}...")
1716
+
1717
+ # Load config file if provided
1718
+ config_dict = {}
1719
+ if config:
1720
+ echo_info(f"Loading configuration from {config}...")
1721
+ config_dict = _load_config_file(config)
1722
+
1723
+ # make sure not to push if use local registry_url
1724
+ if registry_url == "localhost":
1725
+ push = False
1726
+
1727
+ # Merge CLI parameters with config (CLI takes precedence)
1728
+ cli_params = {
1729
+ "name": name,
1730
+ "namespace": namespace,
1731
+ "port": port,
1732
+ "image_name": image_name,
1733
+ "image_tag": image_tag,
1734
+ "registry_url": registry_url,
1735
+ "registry_namespace": registry_namespace,
1736
+ "push_to_registry": push if push else None,
1737
+ "entrypoint": entrypoint,
1738
+ "base_image": base_image,
1739
+ "requirements": requirements,
1740
+ "image_pull_policy": image_pull_policy,
1741
+ "deploy_timeout": deploy_timeout,
1742
+ "health_check": health_check,
1743
+ "platform": platform,
1744
+ "pypi_mirror": pypi_mirror,
1745
+ }
1746
+ merged_config = _merge_config(config_dict, cli_params)
1747
+
1748
+ # Extract parameters with defaults
1749
+ namespace = merged_config.get("namespace", "agentscope-runtime")
1750
+ port = merged_config.get("port", 8080)
1751
+ image_name = merged_config.get("image_name", "agent_llm")
1752
+ image_tag = merged_config.get("image_tag", "latest")
1753
+ registry_url = merged_config.get("registry_url", "localhost")
1754
+ registry_namespace = merged_config.get(
1755
+ "registry_namespace",
1756
+ "agentscope-runtime",
1757
+ )
1758
+ push_to_registry = merged_config.get("push_to_registry", False)
1759
+ entrypoint = merged_config.get("entrypoint")
1760
+ base_image = merged_config.get("base_image")
1761
+ deploy_timeout = merged_config.get("deploy_timeout", 300)
1762
+ health_check = merged_config.get("health_check", True)
1763
+ platform = merged_config.get("platform")
1764
+ pypi_mirror = merged_config.get("pypi_mirror")
1765
+
1766
+ # Handle requirements (can be comma-separated string, list, or file
1767
+ # path)
1768
+ requirements = merged_config.get("requirements")
1769
+ if requirements:
1770
+ if isinstance(requirements, str):
1771
+ # Check if it's a file path
1772
+ if os.path.isfile(requirements):
1773
+ with open(requirements, "r", encoding="utf-8") as f:
1774
+ requirements = [
1775
+ line.strip()
1776
+ for line in f
1777
+ if line.strip() and not line.startswith("#")
1778
+ ]
1779
+ else:
1780
+ # Treat as comma-separated string
1781
+ requirements = [r.strip() for r in requirements.split(",")]
1782
+
1783
+ # Handle extra_packages
1784
+ extra_packages = merged_config.get("extra_packages", [])
1785
+
1786
+ # Handle image_pull_policy
1787
+ image_pull_policy = merged_config.get("image_pull_policy")
1788
+
1789
+ # Build runtime_config from resource parameters
1790
+ runtime_config = merged_config.get("runtime_config", {})
1791
+ if not runtime_config.get("resources"):
1792
+ resources = {}
1793
+ if cpu_request or memory_request:
1794
+ resources["requests"] = {}
1795
+ if cpu_request:
1796
+ resources["requests"]["cpu"] = cpu_request
1797
+ if memory_request:
1798
+ resources["requests"]["memory"] = memory_request
1799
+ if cpu_limit or memory_limit:
1800
+ resources["limits"] = {}
1801
+ if cpu_limit:
1802
+ resources["limits"]["cpu"] = cpu_limit
1803
+ if memory_limit:
1804
+ resources["limits"]["memory"] = memory_limit
1805
+ if resources:
1806
+ runtime_config["resources"] = resources
1807
+
1808
+ if image_pull_policy and "image_pull_policy" not in runtime_config:
1809
+ runtime_config["image_pull_policy"] = image_pull_policy
1810
+
1811
+ # Validate source
1812
+ abs_source, source_type = _validate_source(source)
1813
+
1814
+ # Parse environment variables (from config, env_file, and CLI)
1815
+ environment = merged_config.get("environment", {}).copy()
1816
+ cli_env = _parse_environment(env, env_file)
1817
+ environment.update(cli_env) # CLI env overrides config env
1818
+
1819
+ if environment:
1820
+ echo_info(f"Using {len(environment)} environment variable(s)")
1821
+
1822
+ # Create deployer
1823
+ k8s_config = K8sConfig(
1824
+ k8s_namespace=namespace,
1825
+ kubeconfig_path=kube_config_path,
1826
+ )
1827
+ registry_config = RegistryConfig(
1828
+ registry_url=registry_url,
1829
+ namespace=registry_namespace,
1830
+ )
1831
+ deployer = KnativeDeployManager(
1832
+ kube_config=k8s_config,
1833
+ registry_config=registry_config,
1834
+ )
1835
+
1836
+ # Prepare entrypoint specification
1837
+ if source_type == "directory":
1838
+ # For directory: find entrypoint and create path
1839
+ project_dir = abs_source
1840
+ entry_script = _find_entrypoint(project_dir, entrypoint)
1841
+ entrypoint_spec = os.path.join(project_dir, entry_script)
1842
+
1843
+ echo_info(f"Using project directory: {project_dir}")
1844
+ echo_info(f"Entry script: {entry_script}")
1845
+ else:
1846
+ # For single file: use file path directly
1847
+ entrypoint_spec = abs_source
1848
+
1849
+ echo_info(f"Using file: {abs_source}")
1850
+
1851
+ # Deploy to Knative using entrypoint
1852
+ echo_info("Deploying to Knative...")
1853
+
1854
+ # Build deploy parameters
1855
+ deploy_params = {
1856
+ "entrypoint": entrypoint_spec,
1857
+ "port": port,
1858
+ "image_name": image_name,
1859
+ "image_tag": image_tag,
1860
+ "push_to_registry": push_to_registry,
1861
+ "environment": environment if environment else None,
1862
+ }
1863
+
1864
+ # Add optional parameters if provided
1865
+ if base_image:
1866
+ deploy_params["base_image"] = base_image
1867
+ if requirements:
1868
+ deploy_params["requirements"] = requirements
1869
+ if extra_packages:
1870
+ deploy_params["extra_packages"] = extra_packages
1871
+ if runtime_config:
1872
+ deploy_params["runtime_config"] = runtime_config
1873
+ if deploy_timeout:
1874
+ deploy_params["deploy_timeout"] = deploy_timeout
1875
+ if health_check is not None:
1876
+ deploy_params["health_check"] = health_check
1877
+ if platform:
1878
+ deploy_params["platform"] = platform
1879
+ if pypi_mirror:
1880
+ deploy_params["pypi_mirror"] = pypi_mirror
1881
+
1882
+ # Add agent_source for state saving
1883
+ deploy_params["agent_source"] = abs_source
1884
+ deploy_params["labels"] = {
1885
+ "app": "agent-ksvc",
1886
+ }
1887
+
1888
+ result = asyncio.run(deployer.deploy(**deploy_params))
1889
+
1890
+ deploy_id = result.get("deploy_id")
1891
+ url = result.get("url")
1892
+ resource_name = result.get("resource_name")
1893
+
1894
+ echo_success("Deployment successful!")
1895
+ echo_info(f"Deployment ID: {deploy_id}")
1896
+ echo_info(f"Resource Name: {resource_name}")
1897
+ echo_info(f"URL: {url}")
1898
+ echo_info(f"Namespace: {namespace}")
1899
+
1900
+ except Exception as e:
1901
+ echo_error(f"Deployment failed: {e}")
1902
+ import traceback
1903
+
1904
+ echo_error(traceback.format_exc())
1905
+ sys.exit(1)
1906
+
1907
+
1073
1908
  if __name__ == "__main__":
1074
1909
  deploy()