agentex-sdk 0.1.0a6__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 (289) hide show
  1. agentex/__init__.py +103 -0
  2. agentex/_base_client.py +1992 -0
  3. agentex/_client.py +506 -0
  4. agentex/_compat.py +219 -0
  5. agentex/_constants.py +14 -0
  6. agentex/_exceptions.py +108 -0
  7. agentex/_files.py +123 -0
  8. agentex/_models.py +829 -0
  9. agentex/_qs.py +150 -0
  10. agentex/_resource.py +43 -0
  11. agentex/_response.py +830 -0
  12. agentex/_streaming.py +333 -0
  13. agentex/_types.py +219 -0
  14. agentex/_utils/__init__.py +57 -0
  15. agentex/_utils/_logs.py +25 -0
  16. agentex/_utils/_proxy.py +65 -0
  17. agentex/_utils/_reflection.py +42 -0
  18. agentex/_utils/_resources_proxy.py +24 -0
  19. agentex/_utils/_streams.py +12 -0
  20. agentex/_utils/_sync.py +86 -0
  21. agentex/_utils/_transform.py +447 -0
  22. agentex/_utils/_typing.py +151 -0
  23. agentex/_utils/_utils.py +422 -0
  24. agentex/_version.py +4 -0
  25. agentex/lib/.keep +4 -0
  26. agentex/lib/__init__.py +0 -0
  27. agentex/lib/adk/__init__.py +41 -0
  28. agentex/lib/adk/_modules/__init__.py +0 -0
  29. agentex/lib/adk/_modules/acp.py +247 -0
  30. agentex/lib/adk/_modules/agent_task_tracker.py +176 -0
  31. agentex/lib/adk/_modules/agents.py +77 -0
  32. agentex/lib/adk/_modules/events.py +141 -0
  33. agentex/lib/adk/_modules/messages.py +285 -0
  34. agentex/lib/adk/_modules/state.py +291 -0
  35. agentex/lib/adk/_modules/streaming.py +75 -0
  36. agentex/lib/adk/_modules/tasks.py +124 -0
  37. agentex/lib/adk/_modules/tracing.py +194 -0
  38. agentex/lib/adk/providers/__init__.py +9 -0
  39. agentex/lib/adk/providers/_modules/__init__.py +0 -0
  40. agentex/lib/adk/providers/_modules/litellm.py +232 -0
  41. agentex/lib/adk/providers/_modules/openai.py +416 -0
  42. agentex/lib/adk/providers/_modules/sgp.py +85 -0
  43. agentex/lib/adk/utils/__init__.py +5 -0
  44. agentex/lib/adk/utils/_modules/__init__.py +0 -0
  45. agentex/lib/adk/utils/_modules/templating.py +94 -0
  46. agentex/lib/cli/__init__.py +0 -0
  47. agentex/lib/cli/commands/__init__.py +0 -0
  48. agentex/lib/cli/commands/agents.py +328 -0
  49. agentex/lib/cli/commands/init.py +227 -0
  50. agentex/lib/cli/commands/main.py +33 -0
  51. agentex/lib/cli/commands/secrets.py +169 -0
  52. agentex/lib/cli/commands/tasks.py +118 -0
  53. agentex/lib/cli/commands/uv.py +133 -0
  54. agentex/lib/cli/handlers/__init__.py +0 -0
  55. agentex/lib/cli/handlers/agent_handlers.py +160 -0
  56. agentex/lib/cli/handlers/cleanup_handlers.py +186 -0
  57. agentex/lib/cli/handlers/deploy_handlers.py +351 -0
  58. agentex/lib/cli/handlers/run_handlers.py +452 -0
  59. agentex/lib/cli/handlers/secret_handlers.py +670 -0
  60. agentex/lib/cli/templates/default/.dockerignore.j2 +43 -0
  61. agentex/lib/cli/templates/default/Dockerfile-uv.j2 +42 -0
  62. agentex/lib/cli/templates/default/Dockerfile.j2 +42 -0
  63. agentex/lib/cli/templates/default/README.md.j2 +193 -0
  64. agentex/lib/cli/templates/default/deploy/example.yaml.j2 +55 -0
  65. agentex/lib/cli/templates/default/manifest.yaml.j2 +116 -0
  66. agentex/lib/cli/templates/default/project/acp.py.j2 +29 -0
  67. agentex/lib/cli/templates/default/pyproject.toml.j2 +33 -0
  68. agentex/lib/cli/templates/default/requirements.txt.j2 +5 -0
  69. agentex/lib/cli/templates/deploy/Screenshot 2025-03-19 at 10.36.57/342/200/257AM.png +0 -0
  70. agentex/lib/cli/templates/deploy/example.yaml.j2 +55 -0
  71. agentex/lib/cli/templates/sync/.dockerignore.j2 +43 -0
  72. agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +42 -0
  73. agentex/lib/cli/templates/sync/Dockerfile.j2 +42 -0
  74. agentex/lib/cli/templates/sync/README.md.j2 +293 -0
  75. agentex/lib/cli/templates/sync/deploy/example.yaml.j2 +55 -0
  76. agentex/lib/cli/templates/sync/manifest.yaml.j2 +116 -0
  77. agentex/lib/cli/templates/sync/project/acp.py.j2 +26 -0
  78. agentex/lib/cli/templates/sync/pyproject.toml.j2 +33 -0
  79. agentex/lib/cli/templates/sync/requirements.txt.j2 +5 -0
  80. agentex/lib/cli/templates/temporal/.dockerignore.j2 +43 -0
  81. agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +48 -0
  82. agentex/lib/cli/templates/temporal/Dockerfile.j2 +48 -0
  83. agentex/lib/cli/templates/temporal/README.md.j2 +316 -0
  84. agentex/lib/cli/templates/temporal/deploy/example.yaml.j2 +55 -0
  85. agentex/lib/cli/templates/temporal/manifest.yaml.j2 +137 -0
  86. agentex/lib/cli/templates/temporal/project/acp.py.j2 +30 -0
  87. agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +33 -0
  88. agentex/lib/cli/templates/temporal/project/workflow.py.j2 +66 -0
  89. agentex/lib/cli/templates/temporal/pyproject.toml.j2 +34 -0
  90. agentex/lib/cli/templates/temporal/requirements.txt.j2 +5 -0
  91. agentex/lib/cli/utils/cli_utils.py +14 -0
  92. agentex/lib/cli/utils/credential_utils.py +103 -0
  93. agentex/lib/cli/utils/exceptions.py +6 -0
  94. agentex/lib/cli/utils/kubectl_utils.py +135 -0
  95. agentex/lib/cli/utils/kubernetes_secrets_utils.py +185 -0
  96. agentex/lib/core/__init__.py +0 -0
  97. agentex/lib/core/adapters/__init__.py +0 -0
  98. agentex/lib/core/adapters/llm/__init__.py +1 -0
  99. agentex/lib/core/adapters/llm/adapter_litellm.py +46 -0
  100. agentex/lib/core/adapters/llm/adapter_sgp.py +55 -0
  101. agentex/lib/core/adapters/llm/port.py +24 -0
  102. agentex/lib/core/adapters/streams/adapter_redis.py +128 -0
  103. agentex/lib/core/adapters/streams/port.py +50 -0
  104. agentex/lib/core/clients/__init__.py +1 -0
  105. agentex/lib/core/clients/temporal/__init__.py +0 -0
  106. agentex/lib/core/clients/temporal/temporal_client.py +181 -0
  107. agentex/lib/core/clients/temporal/types.py +47 -0
  108. agentex/lib/core/clients/temporal/utils.py +56 -0
  109. agentex/lib/core/services/__init__.py +0 -0
  110. agentex/lib/core/services/adk/__init__.py +0 -0
  111. agentex/lib/core/services/adk/acp/__init__.py +0 -0
  112. agentex/lib/core/services/adk/acp/acp.py +210 -0
  113. agentex/lib/core/services/adk/agent_task_tracker.py +85 -0
  114. agentex/lib/core/services/adk/agents.py +43 -0
  115. agentex/lib/core/services/adk/events.py +61 -0
  116. agentex/lib/core/services/adk/messages.py +164 -0
  117. agentex/lib/core/services/adk/providers/__init__.py +0 -0
  118. agentex/lib/core/services/adk/providers/litellm.py +256 -0
  119. agentex/lib/core/services/adk/providers/openai.py +723 -0
  120. agentex/lib/core/services/adk/providers/sgp.py +99 -0
  121. agentex/lib/core/services/adk/state.py +120 -0
  122. agentex/lib/core/services/adk/streaming.py +262 -0
  123. agentex/lib/core/services/adk/tasks.py +69 -0
  124. agentex/lib/core/services/adk/tracing.py +36 -0
  125. agentex/lib/core/services/adk/utils/__init__.py +0 -0
  126. agentex/lib/core/services/adk/utils/templating.py +58 -0
  127. agentex/lib/core/temporal/__init__.py +0 -0
  128. agentex/lib/core/temporal/activities/__init__.py +207 -0
  129. agentex/lib/core/temporal/activities/activity_helpers.py +37 -0
  130. agentex/lib/core/temporal/activities/adk/__init__.py +0 -0
  131. agentex/lib/core/temporal/activities/adk/acp/__init__.py +0 -0
  132. agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +86 -0
  133. agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +76 -0
  134. agentex/lib/core/temporal/activities/adk/agents_activities.py +35 -0
  135. agentex/lib/core/temporal/activities/adk/events_activities.py +50 -0
  136. agentex/lib/core/temporal/activities/adk/messages_activities.py +94 -0
  137. agentex/lib/core/temporal/activities/adk/providers/__init__.py +0 -0
  138. agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +71 -0
  139. agentex/lib/core/temporal/activities/adk/providers/openai_activities.py +210 -0
  140. agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +42 -0
  141. agentex/lib/core/temporal/activities/adk/state_activities.py +85 -0
  142. agentex/lib/core/temporal/activities/adk/streaming_activities.py +33 -0
  143. agentex/lib/core/temporal/activities/adk/tasks_activities.py +48 -0
  144. agentex/lib/core/temporal/activities/adk/tracing_activities.py +55 -0
  145. agentex/lib/core/temporal/activities/adk/utils/__init__.py +0 -0
  146. agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +41 -0
  147. agentex/lib/core/temporal/services/__init__.py +0 -0
  148. agentex/lib/core/temporal/services/temporal_task_service.py +69 -0
  149. agentex/lib/core/temporal/types/__init__.py +0 -0
  150. agentex/lib/core/temporal/types/workflow.py +5 -0
  151. agentex/lib/core/temporal/workers/__init__.py +0 -0
  152. agentex/lib/core/temporal/workers/worker.py +162 -0
  153. agentex/lib/core/temporal/workflows/workflow.py +26 -0
  154. agentex/lib/core/tracing/__init__.py +5 -0
  155. agentex/lib/core/tracing/processors/agentex_tracing_processor.py +117 -0
  156. agentex/lib/core/tracing/processors/sgp_tracing_processor.py +119 -0
  157. agentex/lib/core/tracing/processors/tracing_processor_interface.py +40 -0
  158. agentex/lib/core/tracing/trace.py +311 -0
  159. agentex/lib/core/tracing/tracer.py +70 -0
  160. agentex/lib/core/tracing/tracing_processor_manager.py +62 -0
  161. agentex/lib/environment_variables.py +87 -0
  162. agentex/lib/py.typed +0 -0
  163. agentex/lib/sdk/__init__.py +0 -0
  164. agentex/lib/sdk/config/__init__.py +0 -0
  165. agentex/lib/sdk/config/agent_config.py +61 -0
  166. agentex/lib/sdk/config/agent_manifest.py +219 -0
  167. agentex/lib/sdk/config/build_config.py +35 -0
  168. agentex/lib/sdk/config/deployment_config.py +117 -0
  169. agentex/lib/sdk/config/local_development_config.py +56 -0
  170. agentex/lib/sdk/config/project_config.py +103 -0
  171. agentex/lib/sdk/fastacp/__init__.py +3 -0
  172. agentex/lib/sdk/fastacp/base/base_acp_server.py +406 -0
  173. agentex/lib/sdk/fastacp/fastacp.py +74 -0
  174. agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +72 -0
  175. agentex/lib/sdk/fastacp/impl/sync_acp.py +109 -0
  176. agentex/lib/sdk/fastacp/impl/temporal_acp.py +97 -0
  177. agentex/lib/sdk/fastacp/tests/README.md +297 -0
  178. agentex/lib/sdk/fastacp/tests/conftest.py +307 -0
  179. agentex/lib/sdk/fastacp/tests/pytest.ini +10 -0
  180. agentex/lib/sdk/fastacp/tests/run_tests.py +227 -0
  181. agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +450 -0
  182. agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +344 -0
  183. agentex/lib/sdk/fastacp/tests/test_integration.py +477 -0
  184. agentex/lib/sdk/state_machine/__init__.py +6 -0
  185. agentex/lib/sdk/state_machine/noop_workflow.py +21 -0
  186. agentex/lib/sdk/state_machine/state.py +10 -0
  187. agentex/lib/sdk/state_machine/state_machine.py +189 -0
  188. agentex/lib/sdk/state_machine/state_workflow.py +16 -0
  189. agentex/lib/sdk/utils/__init__.py +0 -0
  190. agentex/lib/sdk/utils/messages.py +223 -0
  191. agentex/lib/types/__init__.py +0 -0
  192. agentex/lib/types/acp.py +94 -0
  193. agentex/lib/types/agent_configs.py +79 -0
  194. agentex/lib/types/agent_results.py +29 -0
  195. agentex/lib/types/credentials.py +34 -0
  196. agentex/lib/types/fastacp.py +61 -0
  197. agentex/lib/types/files.py +13 -0
  198. agentex/lib/types/json_rpc.py +49 -0
  199. agentex/lib/types/llm_messages.py +354 -0
  200. agentex/lib/types/task_message_updates.py +171 -0
  201. agentex/lib/types/tracing.py +34 -0
  202. agentex/lib/utils/__init__.py +0 -0
  203. agentex/lib/utils/completions.py +131 -0
  204. agentex/lib/utils/console.py +14 -0
  205. agentex/lib/utils/io.py +29 -0
  206. agentex/lib/utils/iterables.py +14 -0
  207. agentex/lib/utils/json_schema.py +23 -0
  208. agentex/lib/utils/logging.py +31 -0
  209. agentex/lib/utils/mcp.py +17 -0
  210. agentex/lib/utils/model_utils.py +46 -0
  211. agentex/lib/utils/parsing.py +15 -0
  212. agentex/lib/utils/regex.py +6 -0
  213. agentex/lib/utils/temporal.py +13 -0
  214. agentex/py.typed +0 -0
  215. agentex/resources/__init__.py +103 -0
  216. agentex/resources/agents.py +707 -0
  217. agentex/resources/events.py +294 -0
  218. agentex/resources/messages/__init__.py +33 -0
  219. agentex/resources/messages/batch.py +271 -0
  220. agentex/resources/messages/messages.py +492 -0
  221. agentex/resources/spans.py +557 -0
  222. agentex/resources/states.py +544 -0
  223. agentex/resources/tasks.py +615 -0
  224. agentex/resources/tracker.py +384 -0
  225. agentex/types/__init__.py +56 -0
  226. agentex/types/acp_type.py +7 -0
  227. agentex/types/agent.py +29 -0
  228. agentex/types/agent_list_params.py +13 -0
  229. agentex/types/agent_list_response.py +10 -0
  230. agentex/types/agent_rpc_by_name_params.py +21 -0
  231. agentex/types/agent_rpc_params.py +51 -0
  232. agentex/types/agent_rpc_params1.py +21 -0
  233. agentex/types/agent_rpc_response.py +20 -0
  234. agentex/types/agent_rpc_result.py +90 -0
  235. agentex/types/agent_task_tracker.py +34 -0
  236. agentex/types/data_content.py +30 -0
  237. agentex/types/data_content_param.py +31 -0
  238. agentex/types/data_delta.py +14 -0
  239. agentex/types/event.py +29 -0
  240. agentex/types/event_list_params.py +22 -0
  241. agentex/types/event_list_response.py +10 -0
  242. agentex/types/message_author.py +7 -0
  243. agentex/types/message_create_params.py +18 -0
  244. agentex/types/message_list_params.py +14 -0
  245. agentex/types/message_list_response.py +10 -0
  246. agentex/types/message_style.py +7 -0
  247. agentex/types/message_update_params.py +18 -0
  248. agentex/types/messages/__init__.py +8 -0
  249. agentex/types/messages/batch_create_params.py +16 -0
  250. agentex/types/messages/batch_create_response.py +10 -0
  251. agentex/types/messages/batch_update_params.py +16 -0
  252. agentex/types/messages/batch_update_response.py +10 -0
  253. agentex/types/shared/__init__.py +3 -0
  254. agentex/types/shared/task_message_update.py +83 -0
  255. agentex/types/span.py +36 -0
  256. agentex/types/span_create_params.py +40 -0
  257. agentex/types/span_list_params.py +12 -0
  258. agentex/types/span_list_response.py +10 -0
  259. agentex/types/span_update_params.py +37 -0
  260. agentex/types/state.py +25 -0
  261. agentex/types/state_create_params.py +16 -0
  262. agentex/types/state_list_params.py +16 -0
  263. agentex/types/state_list_response.py +10 -0
  264. agentex/types/state_update_params.py +16 -0
  265. agentex/types/task.py +23 -0
  266. agentex/types/task_delete_by_name_response.py +8 -0
  267. agentex/types/task_delete_response.py +8 -0
  268. agentex/types/task_list_response.py +10 -0
  269. agentex/types/task_message.py +33 -0
  270. agentex/types/task_message_content.py +16 -0
  271. agentex/types/task_message_content_param.py +17 -0
  272. agentex/types/task_message_delta.py +16 -0
  273. agentex/types/text_content.py +53 -0
  274. agentex/types/text_content_param.py +54 -0
  275. agentex/types/text_delta.py +14 -0
  276. agentex/types/tool_request_content.py +36 -0
  277. agentex/types/tool_request_content_param.py +37 -0
  278. agentex/types/tool_request_delta.py +18 -0
  279. agentex/types/tool_response_content.py +36 -0
  280. agentex/types/tool_response_content_param.py +36 -0
  281. agentex/types/tool_response_delta.py +18 -0
  282. agentex/types/tracker_list_params.py +16 -0
  283. agentex/types/tracker_list_response.py +10 -0
  284. agentex/types/tracker_update_params.py +19 -0
  285. agentex_sdk-0.1.0a6.dist-info/METADATA +426 -0
  286. agentex_sdk-0.1.0a6.dist-info/RECORD +289 -0
  287. agentex_sdk-0.1.0a6.dist-info/WHEEL +4 -0
  288. agentex_sdk-0.1.0a6.dist-info/entry_points.txt +2 -0
  289. agentex_sdk-0.1.0a6.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,186 @@
1
+ import asyncio
2
+ import os
3
+
4
+ from rich.console import Console
5
+
6
+ from agentex import Agentex
7
+ from agentex.lib.utils.logging import make_logger
8
+
9
+ # Import Temporal client for direct workflow termination
10
+ try:
11
+ from temporalio.client import Client as TemporalClient # type: ignore
12
+ except ImportError:
13
+ TemporalClient = None
14
+
15
+ logger = make_logger(__name__)
16
+ console = Console()
17
+
18
+
19
+ def should_cleanup_on_restart() -> bool:
20
+ """
21
+ Check if cleanup should be performed on restart.
22
+
23
+ Returns True if:
24
+ - ENVIRONMENT=development, OR
25
+ - AUTO_CLEANUP_ON_RESTART=true
26
+ """
27
+ env = os.getenv("ENVIRONMENT", "").lower()
28
+ auto_cleanup = os.getenv("AUTO_CLEANUP_ON_RESTART", "true").lower()
29
+
30
+ return env == "development" or auto_cleanup == "true"
31
+
32
+
33
+ def cleanup_agent_workflows(
34
+ agent_name: str,
35
+ force: bool = False,
36
+ development_only: bool = True
37
+ ) -> None:
38
+ """
39
+ Clean up all running workflows for an agent during development.
40
+
41
+ This cancels (graceful) all running tasks for the specified agent.
42
+ When force=True, directly terminates workflows via Temporal client.
43
+
44
+ Args:
45
+ agent_name: Name of the agent to cleanup workflows for
46
+ force: If True, directly terminate workflows via Temporal client
47
+ development_only: Only perform cleanup in development environment
48
+ """
49
+
50
+ # Safety check - only run in development mode by default
51
+ if development_only and not force and not should_cleanup_on_restart():
52
+ logger.warning("Cleanup skipped - not in development mode. Use --force to override.")
53
+ return
54
+
55
+ method = "terminate (direct)" if force else "cancel (via agent)"
56
+ console.print(f"[blue]Cleaning up workflows for agent '{agent_name}' using {method}...[/blue]")
57
+
58
+ try:
59
+ client = Agentex()
60
+
61
+ # Get all running tasks
62
+ all_tasks = client.tasks.list()
63
+ running_tasks = [task for task in all_tasks if hasattr(task, 'status') and task.status == "RUNNING"]
64
+
65
+ if not running_tasks:
66
+ console.print("[yellow]No running tasks found[/yellow]")
67
+ return
68
+
69
+ console.print(f"[blue]Cleaning up {len(running_tasks)} running task(s) for agent '{agent_name}'...[/blue]")
70
+
71
+ successful_cleanups = 0
72
+ total_tasks = len(running_tasks)
73
+
74
+ for task in running_tasks:
75
+ task_cleanup_success = False
76
+
77
+ if force:
78
+ # Force mode: Do both graceful RPC cancellation AND direct Temporal termination
79
+ rpc_success = False
80
+ temporal_success = False
81
+
82
+ try:
83
+ # First: Graceful cancellation via agent RPC (handles database/agent cleanup)
84
+ cleanup_single_task(client, agent_name, task.id)
85
+ logger.debug(f"Completed RPC cancellation for task {task.id}")
86
+ rpc_success = True
87
+ except Exception as e:
88
+ logger.warning(f"RPC cancellation failed for task {task.id}: {e}")
89
+
90
+ try:
91
+ # Second: Direct Temporal termination (ensures workflow is forcefully stopped)
92
+ asyncio.run(cleanup_single_task_direct(task.id))
93
+ logger.debug(f"Completed Temporal termination for task {task.id}")
94
+ temporal_success = True
95
+ except Exception as e:
96
+ logger.warning(f"Temporal termination failed for task {task.id}: {e}")
97
+
98
+ # Count as success if either operation succeeded
99
+ task_cleanup_success = rpc_success or temporal_success
100
+
101
+ else:
102
+ # Normal mode: Only graceful cancellation via agent RPC
103
+ try:
104
+ cleanup_single_task(client, agent_name, task.id)
105
+ task_cleanup_success = True
106
+ except Exception as e:
107
+ logger.error(f"Failed to cleanup task {task.id}: {e}")
108
+ task_cleanup_success = False
109
+
110
+ if task_cleanup_success:
111
+ successful_cleanups += 1
112
+ logger.debug(f"Successfully cleaned up task {task.id}")
113
+ else:
114
+ logger.error(f"Failed to cleanup task {task.id}")
115
+ # Don't increment successful_cleanups for actual failures
116
+
117
+ if successful_cleanups == total_tasks:
118
+ console.print(f"[green]✓ Successfully cleaned up all {successful_cleanups} task(s) for agent '{agent_name}'[/green]")
119
+ elif successful_cleanups > 0:
120
+ console.print(f"[yellow]⚠ Successfully cleaned up {successful_cleanups}/{total_tasks} task(s) for agent '{agent_name}'[/yellow]")
121
+ else:
122
+ console.print(f"[red]✗ Failed to cleanup any tasks for agent '{agent_name}'[/red]")
123
+
124
+ except Exception as e:
125
+ console.print(f"[red]Agent workflow cleanup failed: {str(e)}[/red]")
126
+ logger.exception("Agent workflow cleanup failed")
127
+ raise
128
+
129
+
130
+ async def cleanup_single_task_direct(task_id: str) -> None:
131
+ """
132
+ Directly terminate a workflow using Temporal client.
133
+
134
+ Args:
135
+ task_id: ID of the task (used as workflow_id)
136
+ """
137
+ if TemporalClient is None:
138
+ raise ImportError("temporalio package not available for direct workflow termination")
139
+
140
+ try:
141
+ # Connect to Temporal server (assumes default localhost:7233)
142
+ client = await TemporalClient.connect("localhost:7233") # type: ignore
143
+
144
+ # Get workflow handle and terminate
145
+ handle = client.get_workflow_handle(workflow_id=task_id) # type: ignore
146
+ await handle.terminate() # type: ignore
147
+
148
+ logger.debug(f"Successfully terminated workflow {task_id} via Temporal client")
149
+
150
+ except Exception as e:
151
+ # Check if the workflow was already completed - this is actually a success case
152
+ if "workflow execution already completed" in str(e).lower():
153
+ logger.debug(f"Workflow {task_id} was already completed - no termination needed")
154
+ return # Don't raise an exception for this case
155
+
156
+ logger.error(f"Failed to terminate workflow {task_id} via Temporal client: {e}")
157
+ raise
158
+
159
+
160
+ def cleanup_single_task(client: Agentex, agent_name: str, task_id: str) -> None:
161
+ """
162
+ Clean up a single task/workflow using agent RPC cancel method.
163
+
164
+ Args:
165
+ client: Agentex client instance
166
+ agent_name: Name of the agent that owns the task
167
+ task_id: ID of the task to cleanup
168
+ """
169
+ try:
170
+ # Use the agent RPC method to cancel the task
171
+ try:
172
+ client.agents.rpc_by_name(
173
+ agent_name=agent_name,
174
+ method="task/cancel",
175
+ params={"task_id": task_id}
176
+ )
177
+ logger.debug(f"Successfully cancelled task {task_id} via agent '{agent_name}'")
178
+ except Exception as e:
179
+ # If RPC cancel fails, try direct task deletion as fallback
180
+ logger.warning(f"RPC task/cancel failed for task {task_id}, trying direct deletion: {e}")
181
+ client.tasks.delete(task_id=task_id)
182
+ logger.debug(f"Successfully deleted task {task_id} directly")
183
+
184
+ except Exception as e:
185
+ logger.warning(f"Failed to cleanup task {task_id}: {e}")
186
+ raise
@@ -0,0 +1,351 @@
1
+ import os
2
+ import subprocess
3
+ import tempfile
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import yaml
8
+ from pydantic import BaseModel, Field
9
+ from rich.console import Console
10
+
11
+ from agentex.lib.cli.utils.exceptions import DeploymentError, HelmError
12
+ from agentex.lib.cli.utils.kubectl_utils import check_and_switch_cluster_context
13
+ from agentex.lib.sdk.config.agent_config import AgentConfig
14
+ from agentex.lib.sdk.config.agent_manifest import AgentManifest
15
+ from agentex.lib.sdk.config.deployment_config import ClusterConfig
16
+ from agentex.lib.utils.logging import make_logger
17
+
18
+ logger = make_logger(__name__)
19
+ console = Console()
20
+
21
+ TEMPORAL_WORKER_KEY = "temporal-worker"
22
+ AGENTEX_AGENTS_HELM_CHART_VERSION = "0.1.2-v2-beta"
23
+
24
+
25
+ class InputDeployOverrides(BaseModel):
26
+ repository: str | None = Field(
27
+ default=None, description="Override the repository for deployment"
28
+ )
29
+ image_tag: str | None = Field(
30
+ default=None, description="Override the image tag for deployment"
31
+ )
32
+
33
+
34
+ def check_helm_installed() -> bool:
35
+ """Check if helm is installed and available"""
36
+ try:
37
+ result = subprocess.run(
38
+ ["helm", "version", "--short"], capture_output=True, text=True, check=True
39
+ )
40
+ logger.info(f"Helm version: {result.stdout.strip()}")
41
+ return True
42
+ except (subprocess.CalledProcessError, FileNotFoundError):
43
+ return False
44
+
45
+
46
+ def add_helm_repo() -> None:
47
+ """Add the agentex helm repository if not already added"""
48
+ try:
49
+ # Check if repo already exists
50
+ result = subprocess.run(
51
+ ["helm", "repo", "list"], capture_output=True, text=True, check=True
52
+ )
53
+
54
+ if "scale-egp" not in result.stdout:
55
+ console.print("Adding agentex helm repository...")
56
+ subprocess.run(
57
+ [
58
+ "helm",
59
+ "repo",
60
+ "add",
61
+ "scale-egp",
62
+ "https://scale-egp-helm-charts-us-west-2.s3.amazonaws.com/charts",
63
+ ],
64
+ check=True,
65
+ )
66
+ else:
67
+ logger.info("Helm repository already exists. Running update...")
68
+
69
+ subprocess.run(["helm", "repo", "update"], check=True)
70
+ console.print("[green]✓[/green] Helm repository update successfully")
71
+
72
+ except subprocess.CalledProcessError as e:
73
+ raise HelmError(f"Failed to add helm repository: {e}") from e
74
+
75
+
76
+ def load_override_config(override_file_path: str | None = None) -> ClusterConfig | None:
77
+ """Load override configuration from specified file path"""
78
+ if not override_file_path:
79
+ return None
80
+
81
+ override_path = Path(override_file_path)
82
+ if not override_path.exists():
83
+ raise DeploymentError(f"Override file not found: {override_file_path}")
84
+
85
+ try:
86
+ with open(override_path) as f:
87
+ config_data = yaml.safe_load(f)
88
+ return ClusterConfig(**config_data) if config_data else None
89
+ except Exception as e:
90
+ raise DeploymentError(
91
+ f"Failed to load override config from {override_file_path}: {e}"
92
+ ) from e
93
+
94
+
95
+ def merge_deployment_configs(
96
+ manifest: AgentManifest,
97
+ cluster_config: ClusterConfig | None,
98
+ deploy_overrides: InputDeployOverrides,
99
+ ) -> dict[str, Any]:
100
+ agent_config: AgentConfig = manifest.agent
101
+
102
+ """Merge global deployment config with cluster-specific overrides into helm values"""
103
+ if not manifest.deployment:
104
+ raise DeploymentError("No deployment configuration found in manifest")
105
+
106
+ repository = deploy_overrides.repository or manifest.deployment.image.repository
107
+ image_tag = deploy_overrides.image_tag or manifest.deployment.image.tag
108
+
109
+ if not repository or not image_tag:
110
+ raise DeploymentError("Repository and image tag are required")
111
+
112
+ # Start with global configuration
113
+ helm_values = {
114
+ "global": {
115
+ "image": {
116
+ "repository": repository,
117
+ "tag": image_tag,
118
+ "pullPolicy": "IfNotPresent",
119
+ },
120
+ "agent": {
121
+ "name": manifest.agent.name,
122
+ "description": manifest.agent.description,
123
+ "acp_type": manifest.agent.acp_type,
124
+ },
125
+ },
126
+ "replicaCount": manifest.deployment.global_config.replicaCount,
127
+ "resources": {
128
+ "requests": {
129
+ "cpu": manifest.deployment.global_config.resources.requests.cpu,
130
+ "memory": manifest.deployment.global_config.resources.requests.memory,
131
+ },
132
+ "limits": {
133
+ "cpu": manifest.deployment.global_config.resources.limits.cpu,
134
+ "memory": manifest.deployment.global_config.resources.limits.memory,
135
+ },
136
+ },
137
+ }
138
+
139
+ # Handle temporal configuration using new helper methods
140
+ if agent_config.is_temporal_agent():
141
+ temporal_config = agent_config.get_temporal_workflow_config()
142
+ if temporal_config:
143
+ helm_values[TEMPORAL_WORKER_KEY] = {}
144
+ helm_values["global"]["workflow"] = {
145
+ "name": temporal_config.name,
146
+ "taskQueue": temporal_config.queue_name,
147
+ }
148
+ helm_values[TEMPORAL_WORKER_KEY]["enabled"] = True
149
+
150
+ secret_env_vars = []
151
+ if agent_config.credentials:
152
+ for credential in agent_config.credentials:
153
+ secret_env_vars.append(
154
+ {
155
+ "name": credential.env_var_name,
156
+ "secretName": credential.secret_name,
157
+ "secretKey": credential.secret_key,
158
+ }
159
+ )
160
+
161
+ helm_values["secretEnvVars"] = secret_env_vars
162
+ if TEMPORAL_WORKER_KEY in helm_values:
163
+ helm_values[TEMPORAL_WORKER_KEY]["secretEnvVars"] = secret_env_vars
164
+
165
+ # Set the agent_config env vars first to the helm values and so then it can be overriden by the cluster config
166
+ if agent_config.env:
167
+ helm_values["env"] = agent_config.env
168
+ if TEMPORAL_WORKER_KEY in helm_values:
169
+ helm_values[TEMPORAL_WORKER_KEY]["env"] = agent_config.env
170
+
171
+ if manifest.deployment and manifest.deployment.imagePullSecrets:
172
+ pull_secrets = [
173
+ pull_secret.to_dict()
174
+ for pull_secret in manifest.deployment.imagePullSecrets
175
+ ]
176
+ helm_values["global"]["imagePullSecrets"] = pull_secrets
177
+ # TODO: Remove this once i bump the chart version again
178
+ helm_values["imagePullSecrets"] = pull_secrets
179
+
180
+ # Apply cluster-specific overrides
181
+ if cluster_config:
182
+ if cluster_config.image:
183
+ if cluster_config.image.repository:
184
+ helm_values["global"]["image"]["repository"] = (
185
+ cluster_config.image.repository
186
+ )
187
+ if cluster_config.image.tag:
188
+ helm_values["global"]["image"]["tag"] = cluster_config.image.tag
189
+
190
+ if cluster_config.replicaCount is not None:
191
+ helm_values["replicaCount"] = cluster_config.replicaCount
192
+
193
+ if cluster_config.resources:
194
+ if cluster_config.resources.requests:
195
+ helm_values["resources"]["requests"].update(
196
+ {
197
+ "cpu": cluster_config.resources.requests.cpu,
198
+ "memory": cluster_config.resources.requests.memory,
199
+ }
200
+ )
201
+ if cluster_config.resources.limits:
202
+ helm_values["resources"]["limits"].update(
203
+ {
204
+ "cpu": cluster_config.resources.limits.cpu,
205
+ "memory": cluster_config.resources.limits.memory,
206
+ }
207
+ )
208
+
209
+ if cluster_config.env:
210
+ helm_values["env"] = cluster_config.env
211
+
212
+ # Apply additional arbitrary overrides
213
+ if cluster_config.additional_overrides:
214
+ _deep_merge(helm_values, cluster_config.additional_overrides)
215
+
216
+ return helm_values
217
+
218
+
219
+ def _deep_merge(base_dict: dict[str, Any], override_dict: dict[str, Any]) -> None:
220
+ """Deep merge override_dict into base_dict"""
221
+ for key, value in override_dict.items():
222
+ if (
223
+ key in base_dict
224
+ and isinstance(base_dict[key], dict)
225
+ and isinstance(value, dict)
226
+ ):
227
+ _deep_merge(base_dict[key], value)
228
+ else:
229
+ base_dict[key] = value
230
+
231
+
232
+ def create_helm_values_file(helm_values: dict[str, Any]) -> str:
233
+ """Create a temporary helm values file"""
234
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
235
+ yaml.dump(helm_values, f, default_flow_style=False)
236
+ return f.name
237
+
238
+
239
+ def deploy_agent(
240
+ manifest_path: str,
241
+ cluster_name: str,
242
+ namespace: str,
243
+ deploy_overrides: InputDeployOverrides,
244
+ override_file_path: str | None = None,
245
+ ) -> None:
246
+ """Deploy an agent using helm"""
247
+
248
+ # Validate prerequisites
249
+ if not check_helm_installed():
250
+ raise DeploymentError("Helm is not installed. Please install helm first.")
251
+
252
+ # Switch to the specified cluster context
253
+ check_and_switch_cluster_context(cluster_name)
254
+
255
+ manifest = AgentManifest.from_yaml(file_path=manifest_path)
256
+ override_config = load_override_config(override_file_path)
257
+
258
+ # Provide feedback about override configuration
259
+ if override_config:
260
+ console.print(f"[green]✓[/green] Using override config: {override_file_path}")
261
+ else:
262
+ console.print(
263
+ "[yellow]ℹ[/yellow] No override config specified, using global defaults"
264
+ )
265
+
266
+ # Add helm repository/update
267
+ add_helm_repo()
268
+
269
+ # Merge configurations
270
+ helm_values = merge_deployment_configs(manifest, override_config, deploy_overrides)
271
+
272
+ # Create values file
273
+ values_file = create_helm_values_file(helm_values)
274
+
275
+ try:
276
+ agent_name = manifest.agent.name
277
+ release_name = agent_name
278
+
279
+ console.print(
280
+ f"Deploying agent [bold]{agent_name}[/bold] to cluster [bold]{cluster_name}[/bold] in namespace [bold]{namespace}[/bold]"
281
+ )
282
+
283
+ # Check if release exists
284
+ try:
285
+ subprocess.run(
286
+ ["helm", "status", release_name, "-n", namespace],
287
+ capture_output=True,
288
+ check=True,
289
+ )
290
+
291
+ # Release exists, do upgrade
292
+ console.print("Existing deployment found, upgrading...")
293
+ command = [
294
+ "helm",
295
+ "upgrade",
296
+ release_name,
297
+ "scale-egp/agentex-agent",
298
+ "--version",
299
+ AGENTEX_AGENTS_HELM_CHART_VERSION,
300
+ "-f",
301
+ values_file,
302
+ "-n",
303
+ namespace,
304
+ "--atomic",
305
+ "--timeout",
306
+ "10m",
307
+ ]
308
+ console.print(f"[blue]ℹ[/blue] Running command: {' '.join(command)}")
309
+ subprocess.run(command, check=True)
310
+ console.print("[green]✓[/green] Agent upgraded successfully")
311
+
312
+ except subprocess.CalledProcessError:
313
+ # Release doesn't exist, do install
314
+ console.print("Installing new deployment...")
315
+ command = [
316
+ "helm",
317
+ "install",
318
+ release_name,
319
+ "scale-egp/agentex-agent",
320
+ "--version",
321
+ AGENTEX_AGENTS_HELM_CHART_VERSION,
322
+ "-f",
323
+ values_file,
324
+ "-n",
325
+ namespace,
326
+ "--create-namespace",
327
+ "--atomic",
328
+ "--timeout",
329
+ "10m",
330
+ ]
331
+ console.print(f"[blue]ℹ[/blue] Running command: {' '.join(command)}")
332
+ subprocess.run(command, check=True)
333
+ console.print("[green]✓[/green] Agent deployed successfully")
334
+
335
+ # Show success message with helpful commands
336
+ console.print("\n[green]🎉 Deployment completed successfully![/green]")
337
+ console.print(
338
+ f"[blue]Check deployment status:[/blue] helm status {release_name} -n {namespace}"
339
+ )
340
+ console.print(
341
+ f"[blue]View logs:[/blue] kubectl logs -l app.kubernetes.io/name=agentex-agent -n {namespace}"
342
+ )
343
+
344
+ except subprocess.CalledProcessError as e:
345
+ raise HelmError(
346
+ f"Helm deployment failed: {e}\n"
347
+ f"Note: Due to --atomic flag, any partial deployment has been automatically rolled back."
348
+ ) from e
349
+ finally:
350
+ # Clean up values file
351
+ os.unlink(values_file)