agentex-sdk 0.2.3__py3-none-any.whl → 0.2.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.
- agentex/_version.py +1 -1
- agentex/lib/adk/_modules/acp.py +2 -1
- agentex/lib/adk/_modules/agent_task_tracker.py +2 -1
- agentex/lib/adk/_modules/agents.py +2 -1
- agentex/lib/adk/_modules/events.py +2 -1
- agentex/lib/adk/_modules/messages.py +2 -1
- agentex/lib/adk/_modules/state.py +2 -1
- agentex/lib/adk/_modules/streaming.py +2 -1
- agentex/lib/adk/_modules/tasks.py +2 -1
- agentex/lib/adk/_modules/tracing.py +2 -1
- agentex/lib/adk/utils/_modules/client.py +43 -0
- agentex/lib/cli/commands/agents.py +3 -3
- agentex/lib/cli/handlers/agent_handlers.py +1 -1
- agentex/lib/cli/handlers/cleanup_handlers.py +9 -15
- agentex/lib/cli/handlers/deploy_handlers.py +28 -4
- agentex/lib/cli/handlers/run_handlers.py +19 -93
- agentex/lib/cli/templates/sync/project/acp.py.j2 +15 -64
- agentex/lib/cli/utils/path_utils.py +143 -0
- agentex/lib/sdk/fastacp/base/base_acp_server.py +11 -1
- agentex/lib/types/converters.py +60 -0
- agentex/resources/agents.py +9 -8
- agentex/resources/messages/messages.py +4 -0
- agentex/resources/tasks.py +9 -10
- agentex/types/__init__.py +1 -2
- agentex/types/message_list_params.py +1 -0
- agentex/types/shared/__init__.py +3 -0
- agentex/types/shared/delete_response.py +11 -0
- {agentex_sdk-0.2.3.dist-info → agentex_sdk-0.2.5.dist-info}/METADATA +2 -2
- {agentex_sdk-0.2.3.dist-info → agentex_sdk-0.2.5.dist-info}/RECORD +32 -29
- agentex/types/task_delete_by_name_response.py +0 -8
- agentex/types/task_delete_response.py +0 -8
- {agentex_sdk-0.2.3.dist-info → agentex_sdk-0.2.5.dist-info}/WHEEL +0 -0
- {agentex_sdk-0.2.3.dist-info → agentex_sdk-0.2.5.dist-info}/entry_points.txt +0 -0
- {agentex_sdk-0.2.3.dist-info → agentex_sdk-0.2.5.dist-info}/licenses/LICENSE +0 -0
agentex/_version.py
CHANGED
agentex/lib/adk/_modules/acp.py
CHANGED
@@ -4,6 +4,7 @@ from typing import Any
|
|
4
4
|
from temporalio.common import RetryPolicy
|
5
5
|
|
6
6
|
from agentex import AsyncAgentex
|
7
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
7
8
|
from agentex.lib.core.services.adk.acp.acp import ACPService
|
8
9
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
9
10
|
from agentex.lib.core.temporal.activities.adk.acp.acp_activities import (
|
@@ -40,7 +41,7 @@ class ACPModule:
|
|
40
41
|
acp_activities (Optional[ACPActivities]): Optional pre-configured ACP activities. If None, will be auto-initialized.
|
41
42
|
"""
|
42
43
|
if acp_service is None:
|
43
|
-
agentex_client =
|
44
|
+
agentex_client = get_async_agentex_client()
|
44
45
|
tracer = AsyncTracer(agentex_client)
|
45
46
|
self._acp_service = ACPService(agentex_client=agentex_client, tracer=tracer)
|
46
47
|
else:
|
@@ -3,6 +3,7 @@ from datetime import timedelta
|
|
3
3
|
from temporalio.common import RetryPolicy
|
4
4
|
|
5
5
|
from agentex import AsyncAgentex
|
6
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
6
7
|
from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService
|
7
8
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
8
9
|
from agentex.lib.core.temporal.activities.adk.agent_task_tracker_activities import (
|
@@ -33,7 +34,7 @@ class AgentTaskTrackerModule:
|
|
33
34
|
agent_task_tracker_service: AgentTaskTrackerService | None = None,
|
34
35
|
):
|
35
36
|
if agent_task_tracker_service is None:
|
36
|
-
agentex_client =
|
37
|
+
agentex_client = get_async_agentex_client()
|
37
38
|
tracer = AsyncTracer(agentex_client)
|
38
39
|
self._agent_task_tracker_service = AgentTaskTrackerService(
|
39
40
|
agentex_client=agentex_client, tracer=tracer
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from datetime import timedelta
|
2
2
|
from typing import Optional
|
3
3
|
|
4
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
4
5
|
from agentex.lib.core.temporal.activities.adk.agents_activities import AgentsActivityName, GetAgentParams
|
5
6
|
from temporalio.common import RetryPolicy
|
6
7
|
|
@@ -28,7 +29,7 @@ class AgentsModule:
|
|
28
29
|
agents_service: Optional[AgentsService] = None,
|
29
30
|
):
|
30
31
|
if agents_service is None:
|
31
|
-
agentex_client =
|
32
|
+
agentex_client = get_async_agentex_client()
|
32
33
|
tracer = AsyncTracer(agentex_client)
|
33
34
|
self._agents_service = AgentsService(agentex_client=agentex_client, tracer=tracer)
|
34
35
|
else:
|
@@ -3,6 +3,7 @@ from datetime import timedelta
|
|
3
3
|
from temporalio.common import RetryPolicy
|
4
4
|
|
5
5
|
from agentex import AsyncAgentex
|
6
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
6
7
|
from agentex.lib.core.services.adk.events import EventsService
|
7
8
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
8
9
|
from agentex.lib.core.temporal.activities.adk.events_activities import (
|
@@ -32,7 +33,7 @@ class EventsModule:
|
|
32
33
|
events_service: EventsService | None = None,
|
33
34
|
):
|
34
35
|
if events_service is None:
|
35
|
-
agentex_client =
|
36
|
+
agentex_client = get_async_agentex_client()
|
36
37
|
tracer = AsyncTracer(agentex_client)
|
37
38
|
self._events_service = EventsService(
|
38
39
|
agentex_client=agentex_client, tracer=tracer
|
@@ -3,6 +3,7 @@ from datetime import timedelta
|
|
3
3
|
from temporalio.common import RetryPolicy
|
4
4
|
|
5
5
|
from agentex import AsyncAgentex
|
6
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
6
7
|
from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository
|
7
8
|
from agentex.lib.core.services.adk.messages import MessagesService
|
8
9
|
from agentex.lib.core.services.adk.streaming import StreamingService
|
@@ -37,7 +38,7 @@ class MessagesModule:
|
|
37
38
|
messages_service: MessagesService | None = None,
|
38
39
|
):
|
39
40
|
if messages_service is None:
|
40
|
-
agentex_client =
|
41
|
+
agentex_client = get_async_agentex_client()
|
41
42
|
stream_repository = RedisStreamRepository()
|
42
43
|
streaming_service = StreamingService(
|
43
44
|
agentex_client=agentex_client,
|
@@ -5,6 +5,7 @@ from pydantic import BaseModel
|
|
5
5
|
from temporalio.common import RetryPolicy
|
6
6
|
|
7
7
|
from agentex import AsyncAgentex
|
8
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
8
9
|
from agentex.lib.core.services.adk.state import StateService
|
9
10
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
10
11
|
from agentex.lib.core.temporal.activities.adk.state_activities import (
|
@@ -36,7 +37,7 @@ class StateModule:
|
|
36
37
|
state_service: StateService | None = None,
|
37
38
|
):
|
38
39
|
if state_service is None:
|
39
|
-
agentex_client =
|
40
|
+
agentex_client = get_async_agentex_client()
|
40
41
|
tracer = AsyncTracer(agentex_client)
|
41
42
|
self._state_service = StateService(
|
42
43
|
agentex_client=agentex_client, tracer=tracer
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from temporalio.common import RetryPolicy
|
2
2
|
|
3
3
|
from agentex import AsyncAgentex
|
4
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
4
5
|
from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository
|
5
6
|
from agentex.lib.core.services.adk.streaming import (
|
6
7
|
StreamingService,
|
@@ -34,7 +35,7 @@ class StreamingModule:
|
|
34
35
|
"""
|
35
36
|
if streaming_service is None:
|
36
37
|
stream_repository = RedisStreamRepository()
|
37
|
-
agentex_client =
|
38
|
+
agentex_client = get_async_agentex_client()
|
38
39
|
self._streaming_service = StreamingService(
|
39
40
|
agentex_client=agentex_client,
|
40
41
|
stream_repository=stream_repository,
|
@@ -3,6 +3,7 @@ from datetime import timedelta
|
|
3
3
|
from temporalio.common import RetryPolicy
|
4
4
|
|
5
5
|
from agentex import AsyncAgentex
|
6
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
6
7
|
from agentex.lib.core.services.adk.tasks import TasksService
|
7
8
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
8
9
|
from agentex.lib.core.temporal.activities.adk.tasks_activities import (
|
@@ -31,7 +32,7 @@ class TasksModule:
|
|
31
32
|
tasks_service: TasksService | None = None,
|
32
33
|
):
|
33
34
|
if tasks_service is None:
|
34
|
-
agentex_client =
|
35
|
+
agentex_client = get_async_agentex_client()
|
35
36
|
tracer = AsyncTracer(agentex_client)
|
36
37
|
self._tasks_service = TasksService(
|
37
38
|
agentex_client=agentex_client, tracer=tracer
|
@@ -6,6 +6,7 @@ from typing import Any
|
|
6
6
|
from temporalio.common import RetryPolicy
|
7
7
|
|
8
8
|
from agentex import AsyncAgentex
|
9
|
+
from agentex.lib.adk.utils._modules.client import get_async_agentex_client
|
9
10
|
from agentex.lib.core.services.adk.tracing import TracingService
|
10
11
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
11
12
|
from agentex.lib.core.temporal.activities.adk.tracing_activities import (
|
@@ -38,7 +39,7 @@ class TracingModule:
|
|
38
39
|
tracing_activities (Optional[TracingActivities]): Optional pre-configured tracing activities. If None, will be auto-initialized.
|
39
40
|
"""
|
40
41
|
if tracing_service is None:
|
41
|
-
agentex_client =
|
42
|
+
agentex_client = get_async_agentex_client()
|
42
43
|
tracer = AsyncTracer(agentex_client)
|
43
44
|
self._tracing_service = TracingService(tracer=tracer)
|
44
45
|
else:
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import threading
|
2
|
+
from typing import Dict, Optional, Any
|
3
|
+
|
4
|
+
from agentex import AsyncAgentex
|
5
|
+
from agentex.lib.environment_variables import EnvironmentVariables, refreshed_environment_variables
|
6
|
+
|
7
|
+
_client: Optional["AsyncAgentex"] = None
|
8
|
+
_cached_headers: Dict[str, str] = {}
|
9
|
+
_init_kwargs: Dict[str, Any] = {}
|
10
|
+
_lock = threading.RLock()
|
11
|
+
|
12
|
+
|
13
|
+
def _build_headers() -> Dict[str, str]:
|
14
|
+
EnvironmentVariables.refresh()
|
15
|
+
if refreshed_environment_variables and getattr(refreshed_environment_variables, "AGENT_ID", None):
|
16
|
+
return {"x-agent-identity": refreshed_environment_variables.AGENT_ID}
|
17
|
+
return {}
|
18
|
+
|
19
|
+
|
20
|
+
def get_async_agentex_client(**kwargs) -> "AsyncAgentex":
|
21
|
+
"""
|
22
|
+
Return a cached AsyncAgentex instance (created synchronously).
|
23
|
+
Each call re-checks env vars and updates client.default_headers if needed.
|
24
|
+
"""
|
25
|
+
global _client, _cached_headers, _init_kwargs
|
26
|
+
|
27
|
+
new_headers = _build_headers()
|
28
|
+
|
29
|
+
with _lock:
|
30
|
+
# First time (or kwargs changed) -> build a new client
|
31
|
+
if _client is None or kwargs != _init_kwargs:
|
32
|
+
_client = AsyncAgentex(default_headers=new_headers.copy(), **kwargs)
|
33
|
+
_cached_headers = new_headers
|
34
|
+
_init_kwargs = dict(kwargs)
|
35
|
+
return _client
|
36
|
+
|
37
|
+
# Same client; maybe headers changed
|
38
|
+
if new_headers != _cached_headers:
|
39
|
+
_cached_headers = new_headers
|
40
|
+
_client.default_headers.clear()
|
41
|
+
_client.default_headers.update(new_headers)
|
42
|
+
|
43
|
+
return _client
|
@@ -141,13 +141,13 @@ def build(
|
|
141
141
|
typer.echo("No registry provided, skipping image build")
|
142
142
|
return
|
143
143
|
|
144
|
-
platform_list = platforms.split(",") if platforms else []
|
144
|
+
platform_list = platforms.split(",") if platforms else ["linux/amd64"]
|
145
145
|
|
146
146
|
try:
|
147
147
|
image_url = build_agent(
|
148
148
|
manifest_path=manifest,
|
149
|
-
registry_url=registry,
|
150
|
-
repository_name=repository_name
|
149
|
+
registry_url=registry,
|
150
|
+
repository_name=repository_name,
|
151
151
|
platforms=platform_list,
|
152
152
|
push=push,
|
153
153
|
secret=secret or "", # Provide default empty string
|
@@ -168,19 +168,13 @@ def cleanup_single_task(client: Agentex, agent_name: str, task_id: str) -> None:
|
|
168
168
|
"""
|
169
169
|
try:
|
170
170
|
# Use the agent RPC method to cancel the task
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
171
|
+
client.agents.rpc_by_name(
|
172
|
+
agent_name=agent_name,
|
173
|
+
method="task/cancel",
|
174
|
+
params={"task_id": task_id}
|
175
|
+
)
|
176
|
+
logger.debug(f"Successfully cancelled task {task_id} via agent '{agent_name}'")
|
177
|
+
|
184
178
|
except Exception as e:
|
185
|
-
logger.warning(f"
|
186
|
-
raise
|
179
|
+
logger.warning(f"RPC task/cancel failed for task {task_id}: {e}")
|
180
|
+
raise
|
@@ -11,6 +11,7 @@ from rich.console import Console
|
|
11
11
|
from agentex.lib.cli.utils.auth_utils import _encode_principal_context
|
12
12
|
from agentex.lib.cli.utils.exceptions import DeploymentError, HelmError
|
13
13
|
from agentex.lib.cli.utils.kubectl_utils import check_and_switch_cluster_context
|
14
|
+
from agentex.lib.cli.utils.path_utils import calculate_docker_acp_module, PathResolutionError
|
14
15
|
from agentex.lib.environment_variables import EnvVarKeys
|
15
16
|
from agentex.lib.sdk.config.agent_config import AgentConfig
|
16
17
|
from agentex.lib.sdk.config.agent_manifest import AgentManifest
|
@@ -100,10 +101,24 @@ def convert_env_vars_dict_to_list(env_vars: dict[str, str]) -> list[dict[str, st
|
|
100
101
|
return [{"name": key, "value": value} for key, value in env_vars.items()]
|
101
102
|
|
102
103
|
|
104
|
+
def add_acp_command_to_helm_values(helm_values: dict[str, Any], manifest: AgentManifest, manifest_path: str) -> None:
|
105
|
+
"""Add dynamic ACP command to helm values based on manifest configuration"""
|
106
|
+
try:
|
107
|
+
docker_acp_module = calculate_docker_acp_module(manifest, manifest_path)
|
108
|
+
# Create the uvicorn command with the correct module path
|
109
|
+
helm_values["command"] = ["uvicorn", f"{docker_acp_module}:acp", "--host", "0.0.0.0", "--port", "8000"]
|
110
|
+
logger.info(f"Using dynamic ACP command: uvicorn {docker_acp_module}:acp")
|
111
|
+
except (PathResolutionError, Exception) as e:
|
112
|
+
# Fallback to default command structure
|
113
|
+
logger.warning(f"Could not calculate dynamic ACP module ({e}), using default: project.acp")
|
114
|
+
helm_values["command"] = ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
|
115
|
+
|
116
|
+
|
103
117
|
def merge_deployment_configs(
|
104
118
|
manifest: AgentManifest,
|
105
119
|
cluster_config: ClusterConfig | None,
|
106
120
|
deploy_overrides: InputDeployOverrides,
|
121
|
+
manifest_path: str,
|
107
122
|
) -> dict[str, Any]:
|
108
123
|
agent_config: AgentConfig = manifest.agent
|
109
124
|
|
@@ -176,9 +191,12 @@ def merge_deployment_configs(
|
|
176
191
|
if TEMPORAL_WORKER_KEY in helm_values:
|
177
192
|
helm_values[TEMPORAL_WORKER_KEY]["env"] = agent_config.env
|
178
193
|
|
179
|
-
|
180
|
-
|
181
|
-
|
194
|
+
# Add auth principal env var if manifest principal is set
|
195
|
+
encoded_principal = _encode_principal_context(manifest)
|
196
|
+
if encoded_principal:
|
197
|
+
if "env" not in helm_values:
|
198
|
+
helm_values["env"] = {}
|
199
|
+
helm_values["env"][EnvVarKeys.AUTH_PRINCIPAL_B64.value] = encoded_principal
|
182
200
|
|
183
201
|
if manifest.deployment and manifest.deployment.imagePullSecrets:
|
184
202
|
pull_secrets = [
|
@@ -228,10 +246,16 @@ def merge_deployment_configs(
|
|
228
246
|
# Convert the env vars to a list of dictionaries
|
229
247
|
if "env" in helm_values:
|
230
248
|
helm_values["env"] = convert_env_vars_dict_to_list(helm_values["env"])
|
249
|
+
|
250
|
+
# Convert the temporal worker env vars to a list of dictionaries
|
231
251
|
if TEMPORAL_WORKER_KEY in helm_values and "env" in helm_values[TEMPORAL_WORKER_KEY]:
|
232
252
|
helm_values[TEMPORAL_WORKER_KEY]["env"] = convert_env_vars_dict_to_list(
|
233
253
|
helm_values[TEMPORAL_WORKER_KEY]["env"]
|
234
254
|
)
|
255
|
+
|
256
|
+
# Add dynamic ACP command based on manifest configuration
|
257
|
+
add_acp_command_to_helm_values(helm_values, manifest, manifest_path)
|
258
|
+
|
235
259
|
print("Deploying with the following helm values: ", helm_values)
|
236
260
|
return helm_values
|
237
261
|
|
@@ -287,7 +311,7 @@ def deploy_agent(
|
|
287
311
|
add_helm_repo()
|
288
312
|
|
289
313
|
# Merge configurations
|
290
|
-
helm_values = merge_deployment_configs(manifest, override_config, deploy_overrides)
|
314
|
+
helm_values = merge_deployment_configs(manifest, override_config, deploy_overrides, manifest_path)
|
291
315
|
|
292
316
|
# Create values file
|
293
317
|
values_file = create_helm_values_file(helm_values)
|
@@ -11,6 +11,11 @@ from agentex.lib.cli.handlers.cleanup_handlers import (
|
|
11
11
|
cleanup_agent_workflows,
|
12
12
|
should_cleanup_on_restart
|
13
13
|
)
|
14
|
+
from agentex.lib.cli.utils.path_utils import (
|
15
|
+
get_file_paths,
|
16
|
+
calculate_uvicorn_target_for_local,
|
17
|
+
)
|
18
|
+
|
14
19
|
from agentex.lib.environment_variables import EnvVarKeys
|
15
20
|
from agentex.lib.sdk.config.agent_manifest import AgentManifest
|
16
21
|
from agentex.lib.utils.logging import make_logger
|
@@ -104,7 +109,10 @@ async def start_temporal_worker_with_reload(
|
|
104
109
|
# PRE-RESTART CLEANUP - NEW!
|
105
110
|
if current_process is not None:
|
106
111
|
# Extract agent name from worker path for cleanup
|
107
|
-
|
112
|
+
|
113
|
+
agent_name = env.get("AGENT_NAME")
|
114
|
+
if agent_name is None:
|
115
|
+
agent_name = worker_path.parent.parent.name
|
108
116
|
|
109
117
|
# Perform cleanup if configured
|
110
118
|
if should_cleanup_on_restart():
|
@@ -180,15 +188,17 @@ async def start_temporal_worker_with_reload(
|
|
180
188
|
|
181
189
|
|
182
190
|
async def start_acp_server(
|
183
|
-
acp_path: Path, port: int, env: dict[str, str]
|
191
|
+
acp_path: Path, port: int, env: dict[str, str], manifest_dir: Path
|
184
192
|
) -> asyncio.subprocess.Process:
|
185
193
|
"""Start the ACP server process"""
|
186
|
-
# Use
|
194
|
+
# Use file path relative to manifest directory if possible
|
195
|
+
uvicorn_target = calculate_uvicorn_target_for_local(acp_path, manifest_dir)
|
196
|
+
|
187
197
|
cmd = [
|
188
198
|
sys.executable,
|
189
199
|
"-m",
|
190
200
|
"uvicorn",
|
191
|
-
f"{
|
201
|
+
f"{uvicorn_target}:acp",
|
192
202
|
"--reload",
|
193
203
|
"--reload-dir",
|
194
204
|
str(acp_path.parent), # Watch the project directory specifically
|
@@ -201,7 +211,7 @@ async def start_acp_server(
|
|
201
211
|
console.print(f"[blue]Starting ACP server from {acp_path} on port {port}...[/blue]")
|
202
212
|
return await asyncio.create_subprocess_exec(
|
203
213
|
*cmd,
|
204
|
-
cwd=
|
214
|
+
cwd=manifest_dir, # Always use manifest directory as CWD for consistency
|
205
215
|
env=env,
|
206
216
|
stdout=asyncio.subprocess.PIPE,
|
207
217
|
stderr=asyncio.subprocess.STDOUT,
|
@@ -218,7 +228,7 @@ async def start_temporal_worker(
|
|
218
228
|
|
219
229
|
return await asyncio.create_subprocess_exec(
|
220
230
|
*cmd,
|
221
|
-
cwd=worker_path.parent,
|
231
|
+
cwd=worker_path.parent, # Use worker directory as CWD for imports to work
|
222
232
|
env=env,
|
223
233
|
stdout=asyncio.subprocess.PIPE,
|
224
234
|
stderr=asyncio.subprocess.STDOUT,
|
@@ -280,8 +290,9 @@ async def run_agent(manifest_path: str):
|
|
280
290
|
)
|
281
291
|
|
282
292
|
# Start ACP server
|
293
|
+
manifest_dir = Path(manifest_path).parent
|
283
294
|
acp_process = await start_acp_server(
|
284
|
-
file_paths["acp"], manifest.local_development.agent.port, agent_env
|
295
|
+
file_paths["acp"], manifest.local_development.agent.port, agent_env, manifest_dir
|
285
296
|
)
|
286
297
|
process_manager.add_process(acp_process)
|
287
298
|
|
@@ -291,7 +302,7 @@ async def run_agent(manifest_path: str):
|
|
291
302
|
tasks = [acp_output_task]
|
292
303
|
|
293
304
|
# Start temporal worker if needed
|
294
|
-
if is_temporal_agent(manifest):
|
305
|
+
if is_temporal_agent(manifest) and file_paths["worker"]:
|
295
306
|
worker_task = await start_temporal_worker_with_reload(file_paths["worker"], agent_env, process_manager)
|
296
307
|
tasks.append(worker_task)
|
297
308
|
|
@@ -323,92 +334,7 @@ async def run_agent(manifest_path: str):
|
|
323
334
|
await process_manager.cleanup_processes()
|
324
335
|
|
325
336
|
|
326
|
-
def resolve_and_validate_path(base_path: Path, configured_path: str, file_type: str) -> Path:
|
327
|
-
"""Resolve and validate a configured path"""
|
328
|
-
path_obj = Path(configured_path)
|
329
|
-
|
330
|
-
if path_obj.is_absolute():
|
331
|
-
# Absolute path - use as-is
|
332
|
-
resolved_path = path_obj
|
333
|
-
else:
|
334
|
-
# Relative path - resolve relative to manifest directory
|
335
|
-
resolved_path = (base_path / configured_path).resolve()
|
336
|
-
|
337
|
-
# Validate the file exists
|
338
|
-
if not resolved_path.exists():
|
339
|
-
raise RunError(
|
340
|
-
f"{file_type} file not found: {resolved_path}\n"
|
341
|
-
f" Configured path: {configured_path}\n"
|
342
|
-
f" Resolved from manifest: {base_path}"
|
343
|
-
)
|
344
|
-
|
345
|
-
# Validate it's actually a file
|
346
|
-
if not resolved_path.is_file():
|
347
|
-
raise RunError(f"{file_type} path is not a file: {resolved_path}")
|
348
|
-
|
349
|
-
return resolved_path
|
350
|
-
|
351
|
-
|
352
|
-
def validate_path_security(resolved_path: Path, manifest_dir: Path) -> None:
|
353
|
-
"""Basic security validation for resolved paths"""
|
354
|
-
try:
|
355
|
-
# Ensure the resolved path is accessible
|
356
|
-
resolved_path.resolve()
|
357
|
-
|
358
|
-
# Optional: Add warnings for paths that go too far up
|
359
|
-
try:
|
360
|
-
# Check if path goes more than 3 levels up from manifest
|
361
|
-
relative_to_manifest = resolved_path.relative_to(manifest_dir.parent.parent.parent)
|
362
|
-
if str(relative_to_manifest).startswith(".."):
|
363
|
-
logger.warning(
|
364
|
-
f"Path goes significantly outside project structure: {resolved_path}"
|
365
|
-
)
|
366
|
-
except ValueError:
|
367
|
-
# Path is outside the tree - that's okay, just log it
|
368
|
-
logger.info(f"Using path outside manifest directory tree: {resolved_path}")
|
369
|
-
|
370
|
-
except Exception as e:
|
371
|
-
raise RunError(f"Path resolution failed: {resolved_path} - {str(e)}") from e
|
372
|
-
|
373
337
|
|
374
|
-
def get_file_paths(manifest: AgentManifest, manifest_path: str) -> dict[str, Path]:
|
375
|
-
"""Get resolved file paths from manifest configuration"""
|
376
|
-
manifest_dir = Path(manifest_path).parent.resolve()
|
377
|
-
|
378
|
-
# Use configured paths or fall back to defaults for backward compatibility
|
379
|
-
if manifest.local_development and manifest.local_development.paths:
|
380
|
-
paths_config = manifest.local_development.paths
|
381
|
-
|
382
|
-
# Resolve ACP path
|
383
|
-
acp_path = resolve_and_validate_path(manifest_dir, paths_config.acp, "ACP server")
|
384
|
-
validate_path_security(acp_path, manifest_dir)
|
385
|
-
|
386
|
-
# Resolve worker path if specified
|
387
|
-
worker_path = None
|
388
|
-
if paths_config.worker:
|
389
|
-
worker_path = resolve_and_validate_path(
|
390
|
-
manifest_dir, paths_config.worker, "Temporal worker"
|
391
|
-
)
|
392
|
-
validate_path_security(worker_path, manifest_dir)
|
393
|
-
else:
|
394
|
-
# Backward compatibility: use old hardcoded structure
|
395
|
-
project_dir = manifest_dir / "project"
|
396
|
-
acp_path = project_dir / "acp.py"
|
397
|
-
worker_path = project_dir / "run_worker.py" if is_temporal_agent(manifest) else None
|
398
|
-
|
399
|
-
# Validate backward compatibility paths
|
400
|
-
if not acp_path.exists():
|
401
|
-
raise RunError(f"ACP file not found: {acp_path}")
|
402
|
-
|
403
|
-
if worker_path and not worker_path.exists():
|
404
|
-
raise RunError(f"Worker file not found: {worker_path}")
|
405
|
-
|
406
|
-
return {
|
407
|
-
"acp": acp_path,
|
408
|
-
"worker": worker_path,
|
409
|
-
"acp_dir": acp_path.parent,
|
410
|
-
"worker_dir": worker_path.parent if worker_path else None,
|
411
|
-
}
|
412
338
|
|
413
339
|
|
414
340
|
def create_agent_environment(manifest: AgentManifest) -> dict[str, str]:
|
@@ -1,75 +1,26 @@
|
|
1
|
-
import
|
2
|
-
from agentex.lib import adk
|
1
|
+
from typing import AsyncGenerator, Union
|
3
2
|
from agentex.lib.sdk.fastacp.fastacp import FastACP
|
4
|
-
from agentex.lib.types.
|
5
|
-
from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams
|
3
|
+
from agentex.lib.types.acp import SendMessageParams
|
6
4
|
|
5
|
+
from agentex.lib.types.task_message_updates import TaskMessageUpdate
|
6
|
+
from agentex.types.task_message_content import TaskMessageContent
|
7
7
|
from agentex.types.text_content import TextContent
|
8
8
|
from agentex.lib.utils.logging import make_logger
|
9
9
|
|
10
10
|
logger = make_logger(__name__)
|
11
11
|
|
12
12
|
|
13
|
-
# Create an ACP server
|
14
|
-
# This sets up the core server that will handle task creation, events, and cancellation
|
13
|
+
# Create an ACP server
|
15
14
|
acp = FastACP.create(
|
16
|
-
acp_type="
|
17
|
-
config=AgenticACPConfig(
|
18
|
-
type="base",
|
19
|
-
),
|
15
|
+
acp_type="sync",
|
20
16
|
)
|
21
17
|
|
22
|
-
@acp.
|
23
|
-
async def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# Acknowledge that the task has been created.
|
32
|
-
await adk.messages.create(
|
33
|
-
task_id=params.task.id,
|
34
|
-
content=TextContent(
|
35
|
-
author="agent",
|
36
|
-
content=f"Hello! I've received your task. Normally you can do some state initialization here, or just pass and do nothing until you get your first event. For now I'm just acknowledging that I've received a task with the following params:\n\n{json.dumps(params.params, indent=2)}.\n\nYou should only see this message once, when the task is created. All subsequent events will be handled by the `on_task_event_send` handler.",
|
37
|
-
),
|
38
|
-
)
|
39
|
-
|
40
|
-
@acp.on_task_event_send
|
41
|
-
async def handle_event_send(params: SendEventParams):
|
42
|
-
# This handler is called whenever a new event (like a message) is sent to the task
|
43
|
-
|
44
|
-
#########################################################
|
45
|
-
# 2. (👋) Echo back the client's message to show it in the UI.
|
46
|
-
#########################################################
|
47
|
-
|
48
|
-
# This is not done by default so the agent developer has full control over what is shown to the user.
|
49
|
-
if params.event.content:
|
50
|
-
await adk.messages.create(task_id=params.task.id, content=params.event.content)
|
51
|
-
|
52
|
-
#########################################################
|
53
|
-
# 3. (👋) Send a simple response message.
|
54
|
-
#########################################################
|
55
|
-
|
56
|
-
# In future tutorials, this is where we'll add more sophisticated response logic.
|
57
|
-
await adk.messages.create(
|
58
|
-
task_id=params.task.id,
|
59
|
-
content=TextContent(
|
60
|
-
author="agent",
|
61
|
-
content=f"Hello! I've received your message. I can't respond right now, but in future tutorials we'll see how you can get me to intelligently respond to your message.",
|
62
|
-
),
|
63
|
-
)
|
64
|
-
|
65
|
-
@acp.on_task_cancel
|
66
|
-
async def handle_task_cancel(params: CancelTaskParams):
|
67
|
-
# This handler is called when a task is cancelled.
|
68
|
-
# It's useful for cleaning up any resources or state associated with the task.
|
69
|
-
|
70
|
-
#########################################################
|
71
|
-
# 4. (👋) Do task cleanup here.
|
72
|
-
#########################################################
|
73
|
-
|
74
|
-
# This is mostly for durable workflows that are cancellable like Temporal, but we will leave it here for demonstration purposes.
|
75
|
-
logger.info(f"Hello! I've received task cancel for task {params.task.id}: {params.task}. This isn't necessary for this example, but it's good to know that it's available.")
|
18
|
+
@acp.on_message_send
|
19
|
+
async def handle_message_send(
|
20
|
+
params: SendMessageParams
|
21
|
+
) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]:
|
22
|
+
"""Default message handler with streaming support"""
|
23
|
+
return TextContent(
|
24
|
+
author="agent",
|
25
|
+
content=f"Hello! I've received your message. Here's a generic response, but in future tutorials we'll see how you can get me to intelligently respond to your message. This is what I heard you say: {params.content.content}",
|
26
|
+
)
|