agentex-sdk 0.2.5__py3-none-any.whl → 0.2.7__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/_base_client.py +4 -1
- agentex/_files.py +4 -4
- agentex/_version.py +1 -1
- agentex/lib/adk/_modules/acp.py +2 -2
- agentex/lib/adk/_modules/agent_task_tracker.py +2 -2
- agentex/lib/adk/_modules/agents.py +2 -2
- agentex/lib/adk/_modules/events.py +2 -2
- agentex/lib/adk/_modules/messages.py +2 -2
- agentex/lib/adk/_modules/state.py +2 -2
- agentex/lib/adk/_modules/streaming.py +2 -2
- agentex/lib/adk/_modules/tasks.py +2 -2
- agentex/lib/adk/_modules/tracing.py +2 -2
- agentex/lib/adk/providers/_modules/litellm.py +2 -1
- agentex/lib/adk/providers/_modules/openai.py +2 -1
- agentex/lib/adk/providers/_modules/sgp.py +2 -1
- agentex/lib/adk/utils/_modules/client.py +21 -35
- agentex/lib/adk/utils/_modules/templating.py +2 -1
- agentex/lib/cli/commands/agents.py +36 -2
- agentex/lib/cli/debug/__init__.py +15 -0
- agentex/lib/cli/debug/debug_config.py +116 -0
- agentex/lib/cli/debug/debug_handlers.py +174 -0
- agentex/lib/cli/handlers/agent_handlers.py +3 -2
- agentex/lib/cli/handlers/deploy_handlers.py +1 -2
- agentex/lib/cli/handlers/run_handlers.py +24 -7
- agentex/lib/cli/templates/default/pyproject.toml.j2 +0 -1
- agentex/lib/cli/templates/sync/pyproject.toml.j2 +0 -1
- agentex/lib/cli/templates/temporal/project/acp.py.j2 +31 -0
- agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +4 -1
- agentex/lib/cli/templates/temporal/pyproject.toml.j2 +1 -1
- agentex/lib/core/services/adk/acp/acp.py +5 -5
- agentex/lib/core/temporal/activities/__init__.py +2 -1
- agentex/lib/core/temporal/workers/worker.py +24 -0
- agentex/lib/core/tracing/processors/agentex_tracing_processor.py +2 -1
- agentex/lib/environment_variables.py +7 -1
- agentex/lib/sdk/fastacp/base/base_acp_server.py +13 -89
- agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +3 -1
- agentex/lib/utils/debug.py +63 -0
- agentex/lib/utils/registration.py +101 -0
- agentex/resources/tasks.py +54 -4
- agentex/types/__init__.py +1 -0
- agentex/types/agent.py +7 -0
- agentex/types/task_list_params.py +14 -0
- {agentex_sdk-0.2.5.dist-info → agentex_sdk-0.2.7.dist-info}/METADATA +32 -1
- {agentex_sdk-0.2.5.dist-info → agentex_sdk-0.2.7.dist-info}/RECORD +47 -41
- {agentex_sdk-0.2.5.dist-info → agentex_sdk-0.2.7.dist-info}/WHEEL +0 -0
- {agentex_sdk-0.2.5.dist-info → agentex_sdk-0.2.7.dist-info}/entry_points.txt +0 -0
- {agentex_sdk-0.2.5.dist-info → agentex_sdk-0.2.7.dist-info}/licenses/LICENSE +0 -0
agentex/_base_client.py
CHANGED
@@ -532,7 +532,10 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
532
532
|
is_body_allowed = options.method.lower() != "get"
|
533
533
|
|
534
534
|
if is_body_allowed:
|
535
|
-
|
535
|
+
if isinstance(json_data, bytes):
|
536
|
+
kwargs["content"] = json_data
|
537
|
+
else:
|
538
|
+
kwargs["json"] = json_data if is_given(json_data) else None
|
536
539
|
kwargs["files"] = files
|
537
540
|
else:
|
538
541
|
headers.pop("Content-Type", None)
|
agentex/_files.py
CHANGED
@@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes:
|
|
69
69
|
return file
|
70
70
|
|
71
71
|
if is_tuple_t(file):
|
72
|
-
return (file[0],
|
72
|
+
return (file[0], read_file_content(file[1]), *file[2:])
|
73
73
|
|
74
74
|
raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
|
75
75
|
|
76
76
|
|
77
|
-
def
|
77
|
+
def read_file_content(file: FileContent) -> HttpxFileContent:
|
78
78
|
if isinstance(file, os.PathLike):
|
79
79
|
return pathlib.Path(file).read_bytes()
|
80
80
|
return file
|
@@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
|
|
111
111
|
return file
|
112
112
|
|
113
113
|
if is_tuple_t(file):
|
114
|
-
return (file[0], await
|
114
|
+
return (file[0], await async_read_file_content(file[1]), *file[2:])
|
115
115
|
|
116
116
|
raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
|
117
117
|
|
118
118
|
|
119
|
-
async def
|
119
|
+
async def async_read_file_content(file: FileContent) -> HttpxFileContent:
|
120
120
|
if isinstance(file, os.PathLike):
|
121
121
|
return await anyio.Path(file).read_bytes()
|
122
122
|
|
agentex/_version.py
CHANGED
agentex/lib/adk/_modules/acp.py
CHANGED
@@ -4,7 +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
|
7
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
8
8
|
from agentex.lib.core.services.adk.acp.acp import ACPService
|
9
9
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
10
10
|
from agentex.lib.core.temporal.activities.adk.acp.acp_activities import (
|
@@ -41,7 +41,7 @@ class ACPModule:
|
|
41
41
|
acp_activities (Optional[ACPActivities]): Optional pre-configured ACP activities. If None, will be auto-initialized.
|
42
42
|
"""
|
43
43
|
if acp_service is None:
|
44
|
-
agentex_client =
|
44
|
+
agentex_client = create_async_agentex_client()
|
45
45
|
tracer = AsyncTracer(agentex_client)
|
46
46
|
self._acp_service = ACPService(agentex_client=agentex_client, tracer=tracer)
|
47
47
|
else:
|
@@ -3,7 +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
|
6
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
7
7
|
from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService
|
8
8
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
9
9
|
from agentex.lib.core.temporal.activities.adk.agent_task_tracker_activities import (
|
@@ -34,7 +34,7 @@ class AgentTaskTrackerModule:
|
|
34
34
|
agent_task_tracker_service: AgentTaskTrackerService | None = None,
|
35
35
|
):
|
36
36
|
if agent_task_tracker_service is None:
|
37
|
-
agentex_client =
|
37
|
+
agentex_client = create_async_agentex_client()
|
38
38
|
tracer = AsyncTracer(agentex_client)
|
39
39
|
self._agent_task_tracker_service = AgentTaskTrackerService(
|
40
40
|
agentex_client=agentex_client, tracer=tracer
|
@@ -1,7 +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
|
4
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
5
5
|
from agentex.lib.core.temporal.activities.adk.agents_activities import AgentsActivityName, GetAgentParams
|
6
6
|
from temporalio.common import RetryPolicy
|
7
7
|
|
@@ -29,7 +29,7 @@ class AgentsModule:
|
|
29
29
|
agents_service: Optional[AgentsService] = None,
|
30
30
|
):
|
31
31
|
if agents_service is None:
|
32
|
-
agentex_client =
|
32
|
+
agentex_client = create_async_agentex_client()
|
33
33
|
tracer = AsyncTracer(agentex_client)
|
34
34
|
self._agents_service = AgentsService(agentex_client=agentex_client, tracer=tracer)
|
35
35
|
else:
|
@@ -3,7 +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
|
6
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
7
7
|
from agentex.lib.core.services.adk.events import EventsService
|
8
8
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
9
9
|
from agentex.lib.core.temporal.activities.adk.events_activities import (
|
@@ -33,7 +33,7 @@ class EventsModule:
|
|
33
33
|
events_service: EventsService | None = None,
|
34
34
|
):
|
35
35
|
if events_service is None:
|
36
|
-
agentex_client =
|
36
|
+
agentex_client = create_async_agentex_client()
|
37
37
|
tracer = AsyncTracer(agentex_client)
|
38
38
|
self._events_service = EventsService(
|
39
39
|
agentex_client=agentex_client, tracer=tracer
|
@@ -3,7 +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
|
6
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
7
7
|
from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository
|
8
8
|
from agentex.lib.core.services.adk.messages import MessagesService
|
9
9
|
from agentex.lib.core.services.adk.streaming import StreamingService
|
@@ -38,7 +38,7 @@ class MessagesModule:
|
|
38
38
|
messages_service: MessagesService | None = None,
|
39
39
|
):
|
40
40
|
if messages_service is None:
|
41
|
-
agentex_client =
|
41
|
+
agentex_client = create_async_agentex_client()
|
42
42
|
stream_repository = RedisStreamRepository()
|
43
43
|
streaming_service = StreamingService(
|
44
44
|
agentex_client=agentex_client,
|
@@ -5,7 +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
|
8
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
9
9
|
from agentex.lib.core.services.adk.state import StateService
|
10
10
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
11
11
|
from agentex.lib.core.temporal.activities.adk.state_activities import (
|
@@ -37,7 +37,7 @@ class StateModule:
|
|
37
37
|
state_service: StateService | None = None,
|
38
38
|
):
|
39
39
|
if state_service is None:
|
40
|
-
agentex_client =
|
40
|
+
agentex_client = create_async_agentex_client()
|
41
41
|
tracer = AsyncTracer(agentex_client)
|
42
42
|
self._state_service = StateService(
|
43
43
|
agentex_client=agentex_client, tracer=tracer
|
@@ -1,7 +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
|
4
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
5
5
|
from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository
|
6
6
|
from agentex.lib.core.services.adk.streaming import (
|
7
7
|
StreamingService,
|
@@ -35,7 +35,7 @@ class StreamingModule:
|
|
35
35
|
"""
|
36
36
|
if streaming_service is None:
|
37
37
|
stream_repository = RedisStreamRepository()
|
38
|
-
agentex_client =
|
38
|
+
agentex_client = create_async_agentex_client()
|
39
39
|
self._streaming_service = StreamingService(
|
40
40
|
agentex_client=agentex_client,
|
41
41
|
stream_repository=stream_repository,
|
@@ -3,7 +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
|
6
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
7
7
|
from agentex.lib.core.services.adk.tasks import TasksService
|
8
8
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
9
9
|
from agentex.lib.core.temporal.activities.adk.tasks_activities import (
|
@@ -32,7 +32,7 @@ class TasksModule:
|
|
32
32
|
tasks_service: TasksService | None = None,
|
33
33
|
):
|
34
34
|
if tasks_service is None:
|
35
|
-
agentex_client =
|
35
|
+
agentex_client = create_async_agentex_client()
|
36
36
|
tracer = AsyncTracer(agentex_client)
|
37
37
|
self._tasks_service = TasksService(
|
38
38
|
agentex_client=agentex_client, tracer=tracer
|
@@ -6,7 +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
|
9
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
10
10
|
from agentex.lib.core.services.adk.tracing import TracingService
|
11
11
|
from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers
|
12
12
|
from agentex.lib.core.temporal.activities.adk.tracing_activities import (
|
@@ -39,7 +39,7 @@ class TracingModule:
|
|
39
39
|
tracing_activities (Optional[TracingActivities]): Optional pre-configured tracing activities. If None, will be auto-initialized.
|
40
40
|
"""
|
41
41
|
if tracing_service is None:
|
42
|
-
agentex_client =
|
42
|
+
agentex_client = create_async_agentex_client()
|
43
43
|
tracer = AsyncTracer(agentex_client)
|
44
44
|
self._tracing_service = TracingService(tracer=tracer)
|
45
45
|
else:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from collections.abc import AsyncGenerator
|
2
2
|
from datetime import timedelta
|
3
3
|
|
4
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
4
5
|
from temporalio.common import RetryPolicy
|
5
6
|
|
6
7
|
from agentex import AsyncAgentex
|
@@ -39,7 +40,7 @@ class LiteLLMModule:
|
|
39
40
|
):
|
40
41
|
if litellm_service is None:
|
41
42
|
# Create default service
|
42
|
-
agentex_client =
|
43
|
+
agentex_client = create_async_agentex_client()
|
43
44
|
stream_repository = RedisStreamRepository()
|
44
45
|
streaming_service = StreamingService(
|
45
46
|
agentex_client=agentex_client,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from datetime import timedelta
|
2
2
|
from typing import Any, Literal
|
3
3
|
|
4
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
4
5
|
from agents import Agent, RunResult, RunResultStreaming
|
5
6
|
from agents.agent import StopAtTools, ToolsToFinalOutputFunction
|
6
7
|
from agents.agent_output import AgentOutputSchemaBase
|
@@ -46,7 +47,7 @@ class OpenAIModule:
|
|
46
47
|
):
|
47
48
|
if openai_service is None:
|
48
49
|
# Create default service
|
49
|
-
agentex_client =
|
50
|
+
agentex_client = create_async_agentex_client()
|
50
51
|
stream_repository = RedisStreamRepository()
|
51
52
|
streaming_service = StreamingService(
|
52
53
|
agentex_client=agentex_client,
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from datetime import timedelta
|
2
2
|
|
3
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
3
4
|
from scale_gp import SGPClient, SGPClientError
|
4
5
|
from temporalio.common import RetryPolicy
|
5
6
|
|
@@ -33,7 +34,7 @@ class SGPModule:
|
|
33
34
|
if sgp_service is None:
|
34
35
|
try:
|
35
36
|
sgp_client = SGPClient()
|
36
|
-
agentex_client =
|
37
|
+
agentex_client = create_async_agentex_client()
|
37
38
|
tracer = AsyncTracer(agentex_client)
|
38
39
|
self._sgp_service = SGPService(sgp_client=sgp_client, tracer=tracer)
|
39
40
|
except SGPClientError:
|
@@ -1,43 +1,29 @@
|
|
1
|
-
import
|
2
|
-
from typing import Dict, Optional, Any
|
1
|
+
import httpx
|
3
2
|
|
4
3
|
from agentex import AsyncAgentex
|
5
|
-
from agentex.lib.environment_variables import EnvironmentVariables
|
4
|
+
from agentex.lib.environment_variables import EnvironmentVariables
|
5
|
+
from agentex.lib.utils.logging import make_logger
|
6
6
|
|
7
|
-
|
8
|
-
_cached_headers: Dict[str, str] = {}
|
9
|
-
_init_kwargs: Dict[str, Any] = {}
|
10
|
-
_lock = threading.RLock()
|
7
|
+
logger = make_logger(__name__)
|
11
8
|
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
return {"x-agent-identity": refreshed_environment_variables.AGENT_ID}
|
17
|
-
return {}
|
10
|
+
class EnvAuth(httpx.Auth):
|
11
|
+
def __init__(self, header_name="x-agent-api-key"):
|
12
|
+
self.header_name = header_name
|
18
13
|
|
14
|
+
def auth_flow(self, request):
|
15
|
+
# This gets called for every request
|
16
|
+
env_vars = EnvironmentVariables.refresh()
|
17
|
+
if env_vars:
|
18
|
+
agent_api_key = env_vars.AGENT_API_KEY
|
19
|
+
if agent_api_key:
|
20
|
+
request.headers[self.header_name] = agent_api_key
|
21
|
+
masked_key = agent_api_key[-4:] if agent_api_key and len(agent_api_key) > 4 else "****"
|
22
|
+
logger.info(f"Adding header {self.header_name}:{masked_key}")
|
23
|
+
yield request
|
19
24
|
|
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
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
26
|
+
def create_async_agentex_client(**kwargs) -> AsyncAgentex:
|
27
|
+
client = AsyncAgentex(**kwargs)
|
28
|
+
client._client.auth = EnvAuth()
|
29
|
+
return client
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from datetime import timedelta
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
+
from agentex.lib.adk.utils._modules.client import create_async_agentex_client
|
4
5
|
from temporalio.common import RetryPolicy
|
5
6
|
|
6
7
|
from agentex import AsyncAgentex
|
@@ -39,7 +40,7 @@ class TemplatingModule:
|
|
39
40
|
templating_service (Optional[TemplatingService]): Optional pre-configured templating service. If None, will be auto-initialized.
|
40
41
|
"""
|
41
42
|
if templating_service is None:
|
42
|
-
agentex_client =
|
43
|
+
agentex_client = create_async_agentex_client()
|
43
44
|
tracer = AsyncTracer(agentex_client)
|
44
45
|
self._templating_service = TemplatingService(tracer=tracer)
|
45
46
|
else:
|
@@ -11,6 +11,7 @@ from agentex.lib.cli.handlers.agent_handlers import (
|
|
11
11
|
build_agent,
|
12
12
|
run_agent,
|
13
13
|
)
|
14
|
+
from agentex.lib.cli.debug import DebugConfig, DebugMode
|
14
15
|
from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows
|
15
16
|
from agentex.lib.cli.handlers.deploy_handlers import (
|
16
17
|
DeploymentError,
|
@@ -171,7 +172,13 @@ def run(
|
|
171
172
|
False,
|
172
173
|
help="Clean up existing workflows for this agent before starting"
|
173
174
|
),
|
174
|
-
|
175
|
+
# Debug options
|
176
|
+
debug: bool = typer.Option(False, help="Enable debug mode for both worker and ACP (disables auto-reload)"),
|
177
|
+
debug_worker: bool = typer.Option(False, help="Enable debug mode for temporal worker only"),
|
178
|
+
debug_acp: bool = typer.Option(False, help="Enable debug mode for ACP server only"),
|
179
|
+
debug_port: int = typer.Option(5678, help="Port for remote debugging (worker uses this, ACP uses port+1)"),
|
180
|
+
wait_for_debugger: bool = typer.Option(False, help="Wait for debugger to attach before starting"),
|
181
|
+
) -> None:
|
175
182
|
"""
|
176
183
|
Run an agent locally from the given manifest.
|
177
184
|
"""
|
@@ -196,8 +203,35 @@ def run(
|
|
196
203
|
console.print(f"[yellow]⚠ Pre-run cleanup failed: {str(e)}[/yellow]")
|
197
204
|
logger.warning(f"Pre-run cleanup failed: {e}")
|
198
205
|
|
206
|
+
# Create debug configuration based on CLI flags
|
207
|
+
debug_config = None
|
208
|
+
if debug or debug_worker or debug_acp:
|
209
|
+
# Determine debug mode
|
210
|
+
if debug:
|
211
|
+
mode = DebugMode.BOTH
|
212
|
+
elif debug_worker and debug_acp:
|
213
|
+
mode = DebugMode.BOTH
|
214
|
+
elif debug_worker:
|
215
|
+
mode = DebugMode.WORKER
|
216
|
+
elif debug_acp:
|
217
|
+
mode = DebugMode.ACP
|
218
|
+
else:
|
219
|
+
mode = DebugMode.NONE
|
220
|
+
|
221
|
+
debug_config = DebugConfig(
|
222
|
+
enabled=True,
|
223
|
+
mode=mode,
|
224
|
+
port=debug_port,
|
225
|
+
wait_for_attach=wait_for_debugger,
|
226
|
+
auto_port=False # Use fixed port to match VS Code launch.json
|
227
|
+
)
|
228
|
+
|
229
|
+
console.print(f"[blue]🐛 Debug mode enabled: {mode.value}[/blue]")
|
230
|
+
if wait_for_debugger:
|
231
|
+
console.print("[yellow]⏳ Processes will wait for debugger attachment[/yellow]")
|
232
|
+
|
199
233
|
try:
|
200
|
-
run_agent(manifest_path=manifest)
|
234
|
+
run_agent(manifest_path=manifest, debug_config=debug_config)
|
201
235
|
except Exception as e:
|
202
236
|
typer.echo(f"Error running agent: {str(e)}", err=True)
|
203
237
|
logger.exception("Error running agent")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
"""
|
2
|
+
Debug functionality for AgentEx CLI
|
3
|
+
|
4
|
+
Provides debug support for temporal workers and ACP servers during local development.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .debug_config import DebugConfig, DebugMode
|
8
|
+
from .debug_handlers import start_acp_server_debug, start_temporal_worker_debug
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"DebugConfig",
|
12
|
+
"DebugMode",
|
13
|
+
"start_acp_server_debug",
|
14
|
+
"start_temporal_worker_debug",
|
15
|
+
]
|
@@ -0,0 +1,116 @@
|
|
1
|
+
"""
|
2
|
+
Debug configuration models for AgentEx CLI debugging.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import socket
|
6
|
+
from enum import Enum
|
7
|
+
from typing import Optional
|
8
|
+
|
9
|
+
from agentex.lib.utils.model_utils import BaseModel
|
10
|
+
|
11
|
+
|
12
|
+
class DebugMode(str, Enum):
|
13
|
+
"""Debug mode options"""
|
14
|
+
WORKER = "worker"
|
15
|
+
ACP = "acp"
|
16
|
+
BOTH = "both"
|
17
|
+
NONE = "none"
|
18
|
+
|
19
|
+
|
20
|
+
class DebugConfig(BaseModel):
|
21
|
+
"""Configuration for debug mode"""
|
22
|
+
|
23
|
+
enabled: bool = False
|
24
|
+
mode: DebugMode = DebugMode.NONE
|
25
|
+
port: int = 5678
|
26
|
+
wait_for_attach: bool = False
|
27
|
+
auto_port: bool = True # Automatically find available port if specified port is busy
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def create_worker_debug(
|
31
|
+
cls,
|
32
|
+
port: int = 5678,
|
33
|
+
wait_for_attach: bool = False,
|
34
|
+
auto_port: bool = True
|
35
|
+
) -> "DebugConfig":
|
36
|
+
"""Create debug config for worker debugging"""
|
37
|
+
return cls(
|
38
|
+
enabled=True,
|
39
|
+
mode=DebugMode.WORKER,
|
40
|
+
port=port,
|
41
|
+
wait_for_attach=wait_for_attach,
|
42
|
+
auto_port=auto_port
|
43
|
+
)
|
44
|
+
|
45
|
+
@classmethod
|
46
|
+
def create_acp_debug(
|
47
|
+
cls,
|
48
|
+
port: int = 5679,
|
49
|
+
wait_for_attach: bool = False,
|
50
|
+
auto_port: bool = True
|
51
|
+
) -> "DebugConfig":
|
52
|
+
"""Create debug config for ACP debugging"""
|
53
|
+
return cls(
|
54
|
+
enabled=True,
|
55
|
+
mode=DebugMode.ACP,
|
56
|
+
port=port,
|
57
|
+
wait_for_attach=wait_for_attach,
|
58
|
+
auto_port=auto_port
|
59
|
+
)
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
def create_both_debug(
|
63
|
+
cls,
|
64
|
+
worker_port: int = 5678,
|
65
|
+
acp_port: int = 5679,
|
66
|
+
wait_for_attach: bool = False,
|
67
|
+
auto_port: bool = True
|
68
|
+
) -> "DebugConfig":
|
69
|
+
"""Create debug config for both worker and ACP debugging"""
|
70
|
+
return cls(
|
71
|
+
enabled=True,
|
72
|
+
mode=DebugMode.BOTH,
|
73
|
+
port=worker_port, # Primary port for worker
|
74
|
+
wait_for_attach=wait_for_attach,
|
75
|
+
auto_port=auto_port
|
76
|
+
)
|
77
|
+
|
78
|
+
def should_debug_worker(self) -> bool:
|
79
|
+
"""Check if worker should be debugged"""
|
80
|
+
return self.enabled and self.mode in (DebugMode.WORKER, DebugMode.BOTH)
|
81
|
+
|
82
|
+
def should_debug_acp(self) -> bool:
|
83
|
+
"""Check if ACP should be debugged"""
|
84
|
+
return self.enabled and self.mode in (DebugMode.ACP, DebugMode.BOTH)
|
85
|
+
|
86
|
+
def get_worker_port(self) -> int:
|
87
|
+
"""Get port for worker debugging"""
|
88
|
+
return self.port
|
89
|
+
|
90
|
+
def get_acp_port(self) -> int:
|
91
|
+
"""Get port for ACP debugging"""
|
92
|
+
if self.mode == DebugMode.BOTH:
|
93
|
+
return self.port + 1 # Use port + 1 for ACP when debugging both
|
94
|
+
return self.port
|
95
|
+
|
96
|
+
|
97
|
+
def find_available_port(start_port: int = 5678, max_attempts: int = 10) -> int:
|
98
|
+
"""Find an available port starting from start_port"""
|
99
|
+
for port in range(start_port, start_port + max_attempts):
|
100
|
+
try:
|
101
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
102
|
+
s.bind(('localhost', port))
|
103
|
+
return port
|
104
|
+
except OSError:
|
105
|
+
continue
|
106
|
+
|
107
|
+
# If we can't find an available port, just return the start port
|
108
|
+
# and let the debug server handle the error
|
109
|
+
return start_port
|
110
|
+
|
111
|
+
|
112
|
+
def resolve_debug_port(config: DebugConfig, target_port: int) -> int:
|
113
|
+
"""Resolve the actual port to use for debugging"""
|
114
|
+
if config.auto_port:
|
115
|
+
return find_available_port(target_port)
|
116
|
+
return target_port
|