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.
- agentex/__init__.py +103 -0
- agentex/_base_client.py +1992 -0
- agentex/_client.py +506 -0
- agentex/_compat.py +219 -0
- agentex/_constants.py +14 -0
- agentex/_exceptions.py +108 -0
- agentex/_files.py +123 -0
- agentex/_models.py +829 -0
- agentex/_qs.py +150 -0
- agentex/_resource.py +43 -0
- agentex/_response.py +830 -0
- agentex/_streaming.py +333 -0
- agentex/_types.py +219 -0
- agentex/_utils/__init__.py +57 -0
- agentex/_utils/_logs.py +25 -0
- agentex/_utils/_proxy.py +65 -0
- agentex/_utils/_reflection.py +42 -0
- agentex/_utils/_resources_proxy.py +24 -0
- agentex/_utils/_streams.py +12 -0
- agentex/_utils/_sync.py +86 -0
- agentex/_utils/_transform.py +447 -0
- agentex/_utils/_typing.py +151 -0
- agentex/_utils/_utils.py +422 -0
- agentex/_version.py +4 -0
- agentex/lib/.keep +4 -0
- agentex/lib/__init__.py +0 -0
- agentex/lib/adk/__init__.py +41 -0
- agentex/lib/adk/_modules/__init__.py +0 -0
- agentex/lib/adk/_modules/acp.py +247 -0
- agentex/lib/adk/_modules/agent_task_tracker.py +176 -0
- agentex/lib/adk/_modules/agents.py +77 -0
- agentex/lib/adk/_modules/events.py +141 -0
- agentex/lib/adk/_modules/messages.py +285 -0
- agentex/lib/adk/_modules/state.py +291 -0
- agentex/lib/adk/_modules/streaming.py +75 -0
- agentex/lib/adk/_modules/tasks.py +124 -0
- agentex/lib/adk/_modules/tracing.py +194 -0
- agentex/lib/adk/providers/__init__.py +9 -0
- agentex/lib/adk/providers/_modules/__init__.py +0 -0
- agentex/lib/adk/providers/_modules/litellm.py +232 -0
- agentex/lib/adk/providers/_modules/openai.py +416 -0
- agentex/lib/adk/providers/_modules/sgp.py +85 -0
- agentex/lib/adk/utils/__init__.py +5 -0
- agentex/lib/adk/utils/_modules/__init__.py +0 -0
- agentex/lib/adk/utils/_modules/templating.py +94 -0
- agentex/lib/cli/__init__.py +0 -0
- agentex/lib/cli/commands/__init__.py +0 -0
- agentex/lib/cli/commands/agents.py +328 -0
- agentex/lib/cli/commands/init.py +227 -0
- agentex/lib/cli/commands/main.py +33 -0
- agentex/lib/cli/commands/secrets.py +169 -0
- agentex/lib/cli/commands/tasks.py +118 -0
- agentex/lib/cli/commands/uv.py +133 -0
- agentex/lib/cli/handlers/__init__.py +0 -0
- agentex/lib/cli/handlers/agent_handlers.py +160 -0
- agentex/lib/cli/handlers/cleanup_handlers.py +186 -0
- agentex/lib/cli/handlers/deploy_handlers.py +351 -0
- agentex/lib/cli/handlers/run_handlers.py +452 -0
- agentex/lib/cli/handlers/secret_handlers.py +670 -0
- agentex/lib/cli/templates/default/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/default/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/default/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/default/README.md.j2 +193 -0
- agentex/lib/cli/templates/default/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/default/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/default/project/acp.py.j2 +29 -0
- agentex/lib/cli/templates/default/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/default/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/deploy/Screenshot 2025-03-19 at 10.36.57/342/200/257AM.png +0 -0
- agentex/lib/cli/templates/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/sync/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/sync/README.md.j2 +293 -0
- agentex/lib/cli/templates/sync/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/sync/project/acp.py.j2 +26 -0
- agentex/lib/cli/templates/sync/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/sync/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/temporal/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +48 -0
- agentex/lib/cli/templates/temporal/Dockerfile.j2 +48 -0
- agentex/lib/cli/templates/temporal/README.md.j2 +316 -0
- agentex/lib/cli/templates/temporal/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/temporal/manifest.yaml.j2 +137 -0
- agentex/lib/cli/templates/temporal/project/acp.py.j2 +30 -0
- agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +33 -0
- agentex/lib/cli/templates/temporal/project/workflow.py.j2 +66 -0
- agentex/lib/cli/templates/temporal/pyproject.toml.j2 +34 -0
- agentex/lib/cli/templates/temporal/requirements.txt.j2 +5 -0
- agentex/lib/cli/utils/cli_utils.py +14 -0
- agentex/lib/cli/utils/credential_utils.py +103 -0
- agentex/lib/cli/utils/exceptions.py +6 -0
- agentex/lib/cli/utils/kubectl_utils.py +135 -0
- agentex/lib/cli/utils/kubernetes_secrets_utils.py +185 -0
- agentex/lib/core/__init__.py +0 -0
- agentex/lib/core/adapters/__init__.py +0 -0
- agentex/lib/core/adapters/llm/__init__.py +1 -0
- agentex/lib/core/adapters/llm/adapter_litellm.py +46 -0
- agentex/lib/core/adapters/llm/adapter_sgp.py +55 -0
- agentex/lib/core/adapters/llm/port.py +24 -0
- agentex/lib/core/adapters/streams/adapter_redis.py +128 -0
- agentex/lib/core/adapters/streams/port.py +50 -0
- agentex/lib/core/clients/__init__.py +1 -0
- agentex/lib/core/clients/temporal/__init__.py +0 -0
- agentex/lib/core/clients/temporal/temporal_client.py +181 -0
- agentex/lib/core/clients/temporal/types.py +47 -0
- agentex/lib/core/clients/temporal/utils.py +56 -0
- agentex/lib/core/services/__init__.py +0 -0
- agentex/lib/core/services/adk/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/acp.py +210 -0
- agentex/lib/core/services/adk/agent_task_tracker.py +85 -0
- agentex/lib/core/services/adk/agents.py +43 -0
- agentex/lib/core/services/adk/events.py +61 -0
- agentex/lib/core/services/adk/messages.py +164 -0
- agentex/lib/core/services/adk/providers/__init__.py +0 -0
- agentex/lib/core/services/adk/providers/litellm.py +256 -0
- agentex/lib/core/services/adk/providers/openai.py +723 -0
- agentex/lib/core/services/adk/providers/sgp.py +99 -0
- agentex/lib/core/services/adk/state.py +120 -0
- agentex/lib/core/services/adk/streaming.py +262 -0
- agentex/lib/core/services/adk/tasks.py +69 -0
- agentex/lib/core/services/adk/tracing.py +36 -0
- agentex/lib/core/services/adk/utils/__init__.py +0 -0
- agentex/lib/core/services/adk/utils/templating.py +58 -0
- agentex/lib/core/temporal/__init__.py +0 -0
- agentex/lib/core/temporal/activities/__init__.py +207 -0
- agentex/lib/core/temporal/activities/activity_helpers.py +37 -0
- agentex/lib/core/temporal/activities/adk/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +86 -0
- agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +76 -0
- agentex/lib/core/temporal/activities/adk/agents_activities.py +35 -0
- agentex/lib/core/temporal/activities/adk/events_activities.py +50 -0
- agentex/lib/core/temporal/activities/adk/messages_activities.py +94 -0
- agentex/lib/core/temporal/activities/adk/providers/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +71 -0
- agentex/lib/core/temporal/activities/adk/providers/openai_activities.py +210 -0
- agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +42 -0
- agentex/lib/core/temporal/activities/adk/state_activities.py +85 -0
- agentex/lib/core/temporal/activities/adk/streaming_activities.py +33 -0
- agentex/lib/core/temporal/activities/adk/tasks_activities.py +48 -0
- agentex/lib/core/temporal/activities/adk/tracing_activities.py +55 -0
- agentex/lib/core/temporal/activities/adk/utils/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +41 -0
- agentex/lib/core/temporal/services/__init__.py +0 -0
- agentex/lib/core/temporal/services/temporal_task_service.py +69 -0
- agentex/lib/core/temporal/types/__init__.py +0 -0
- agentex/lib/core/temporal/types/workflow.py +5 -0
- agentex/lib/core/temporal/workers/__init__.py +0 -0
- agentex/lib/core/temporal/workers/worker.py +162 -0
- agentex/lib/core/temporal/workflows/workflow.py +26 -0
- agentex/lib/core/tracing/__init__.py +5 -0
- agentex/lib/core/tracing/processors/agentex_tracing_processor.py +117 -0
- agentex/lib/core/tracing/processors/sgp_tracing_processor.py +119 -0
- agentex/lib/core/tracing/processors/tracing_processor_interface.py +40 -0
- agentex/lib/core/tracing/trace.py +311 -0
- agentex/lib/core/tracing/tracer.py +70 -0
- agentex/lib/core/tracing/tracing_processor_manager.py +62 -0
- agentex/lib/environment_variables.py +87 -0
- agentex/lib/py.typed +0 -0
- agentex/lib/sdk/__init__.py +0 -0
- agentex/lib/sdk/config/__init__.py +0 -0
- agentex/lib/sdk/config/agent_config.py +61 -0
- agentex/lib/sdk/config/agent_manifest.py +219 -0
- agentex/lib/sdk/config/build_config.py +35 -0
- agentex/lib/sdk/config/deployment_config.py +117 -0
- agentex/lib/sdk/config/local_development_config.py +56 -0
- agentex/lib/sdk/config/project_config.py +103 -0
- agentex/lib/sdk/fastacp/__init__.py +3 -0
- agentex/lib/sdk/fastacp/base/base_acp_server.py +406 -0
- agentex/lib/sdk/fastacp/fastacp.py +74 -0
- agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +72 -0
- agentex/lib/sdk/fastacp/impl/sync_acp.py +109 -0
- agentex/lib/sdk/fastacp/impl/temporal_acp.py +97 -0
- agentex/lib/sdk/fastacp/tests/README.md +297 -0
- agentex/lib/sdk/fastacp/tests/conftest.py +307 -0
- agentex/lib/sdk/fastacp/tests/pytest.ini +10 -0
- agentex/lib/sdk/fastacp/tests/run_tests.py +227 -0
- agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +450 -0
- agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +344 -0
- agentex/lib/sdk/fastacp/tests/test_integration.py +477 -0
- agentex/lib/sdk/state_machine/__init__.py +6 -0
- agentex/lib/sdk/state_machine/noop_workflow.py +21 -0
- agentex/lib/sdk/state_machine/state.py +10 -0
- agentex/lib/sdk/state_machine/state_machine.py +189 -0
- agentex/lib/sdk/state_machine/state_workflow.py +16 -0
- agentex/lib/sdk/utils/__init__.py +0 -0
- agentex/lib/sdk/utils/messages.py +223 -0
- agentex/lib/types/__init__.py +0 -0
- agentex/lib/types/acp.py +94 -0
- agentex/lib/types/agent_configs.py +79 -0
- agentex/lib/types/agent_results.py +29 -0
- agentex/lib/types/credentials.py +34 -0
- agentex/lib/types/fastacp.py +61 -0
- agentex/lib/types/files.py +13 -0
- agentex/lib/types/json_rpc.py +49 -0
- agentex/lib/types/llm_messages.py +354 -0
- agentex/lib/types/task_message_updates.py +171 -0
- agentex/lib/types/tracing.py +34 -0
- agentex/lib/utils/__init__.py +0 -0
- agentex/lib/utils/completions.py +131 -0
- agentex/lib/utils/console.py +14 -0
- agentex/lib/utils/io.py +29 -0
- agentex/lib/utils/iterables.py +14 -0
- agentex/lib/utils/json_schema.py +23 -0
- agentex/lib/utils/logging.py +31 -0
- agentex/lib/utils/mcp.py +17 -0
- agentex/lib/utils/model_utils.py +46 -0
- agentex/lib/utils/parsing.py +15 -0
- agentex/lib/utils/regex.py +6 -0
- agentex/lib/utils/temporal.py +13 -0
- agentex/py.typed +0 -0
- agentex/resources/__init__.py +103 -0
- agentex/resources/agents.py +707 -0
- agentex/resources/events.py +294 -0
- agentex/resources/messages/__init__.py +33 -0
- agentex/resources/messages/batch.py +271 -0
- agentex/resources/messages/messages.py +492 -0
- agentex/resources/spans.py +557 -0
- agentex/resources/states.py +544 -0
- agentex/resources/tasks.py +615 -0
- agentex/resources/tracker.py +384 -0
- agentex/types/__init__.py +56 -0
- agentex/types/acp_type.py +7 -0
- agentex/types/agent.py +29 -0
- agentex/types/agent_list_params.py +13 -0
- agentex/types/agent_list_response.py +10 -0
- agentex/types/agent_rpc_by_name_params.py +21 -0
- agentex/types/agent_rpc_params.py +51 -0
- agentex/types/agent_rpc_params1.py +21 -0
- agentex/types/agent_rpc_response.py +20 -0
- agentex/types/agent_rpc_result.py +90 -0
- agentex/types/agent_task_tracker.py +34 -0
- agentex/types/data_content.py +30 -0
- agentex/types/data_content_param.py +31 -0
- agentex/types/data_delta.py +14 -0
- agentex/types/event.py +29 -0
- agentex/types/event_list_params.py +22 -0
- agentex/types/event_list_response.py +10 -0
- agentex/types/message_author.py +7 -0
- agentex/types/message_create_params.py +18 -0
- agentex/types/message_list_params.py +14 -0
- agentex/types/message_list_response.py +10 -0
- agentex/types/message_style.py +7 -0
- agentex/types/message_update_params.py +18 -0
- agentex/types/messages/__init__.py +8 -0
- agentex/types/messages/batch_create_params.py +16 -0
- agentex/types/messages/batch_create_response.py +10 -0
- agentex/types/messages/batch_update_params.py +16 -0
- agentex/types/messages/batch_update_response.py +10 -0
- agentex/types/shared/__init__.py +3 -0
- agentex/types/shared/task_message_update.py +83 -0
- agentex/types/span.py +36 -0
- agentex/types/span_create_params.py +40 -0
- agentex/types/span_list_params.py +12 -0
- agentex/types/span_list_response.py +10 -0
- agentex/types/span_update_params.py +37 -0
- agentex/types/state.py +25 -0
- agentex/types/state_create_params.py +16 -0
- agentex/types/state_list_params.py +16 -0
- agentex/types/state_list_response.py +10 -0
- agentex/types/state_update_params.py +16 -0
- agentex/types/task.py +23 -0
- agentex/types/task_delete_by_name_response.py +8 -0
- agentex/types/task_delete_response.py +8 -0
- agentex/types/task_list_response.py +10 -0
- agentex/types/task_message.py +33 -0
- agentex/types/task_message_content.py +16 -0
- agentex/types/task_message_content_param.py +17 -0
- agentex/types/task_message_delta.py +16 -0
- agentex/types/text_content.py +53 -0
- agentex/types/text_content_param.py +54 -0
- agentex/types/text_delta.py +14 -0
- agentex/types/tool_request_content.py +36 -0
- agentex/types/tool_request_content_param.py +37 -0
- agentex/types/tool_request_delta.py +18 -0
- agentex/types/tool_response_content.py +36 -0
- agentex/types/tool_response_content_param.py +36 -0
- agentex/types/tool_response_delta.py +18 -0
- agentex/types/tracker_list_params.py +16 -0
- agentex/types/tracker_list_response.py +10 -0
- agentex/types/tracker_update_params.py +19 -0
- agentex_sdk-0.1.0a6.dist-info/METADATA +426 -0
- agentex_sdk-0.1.0a6.dist-info/RECORD +289 -0
- agentex_sdk-0.1.0a6.dist-info/WHEEL +4 -0
- agentex_sdk-0.1.0a6.dist-info/entry_points.txt +2 -0
- 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)
|