agentex-sdk 0.2.6__py3-none-any.whl → 0.2.8__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 (30) hide show
  1. agentex/_version.py +1 -1
  2. agentex/lib/adk/utils/_modules/client.py +6 -5
  3. agentex/lib/cli/commands/agents.py +36 -2
  4. agentex/lib/cli/debug/__init__.py +15 -0
  5. agentex/lib/cli/debug/debug_config.py +116 -0
  6. agentex/lib/cli/debug/debug_handlers.py +174 -0
  7. agentex/lib/cli/handlers/agent_handlers.py +3 -2
  8. agentex/lib/cli/handlers/deploy_handlers.py +92 -44
  9. agentex/lib/cli/handlers/run_handlers.py +24 -7
  10. agentex/lib/cli/templates/default/Dockerfile.j2 +2 -0
  11. agentex/lib/cli/templates/default/pyproject.toml.j2 +0 -1
  12. agentex/lib/cli/templates/sync/Dockerfile.j2 +2 -0
  13. agentex/lib/cli/templates/sync/pyproject.toml.j2 +0 -1
  14. agentex/lib/cli/templates/temporal/Dockerfile.j2 +2 -0
  15. agentex/lib/cli/templates/temporal/project/acp.py.j2 +31 -0
  16. agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +4 -1
  17. agentex/lib/cli/templates/temporal/pyproject.toml.j2 +1 -1
  18. agentex/lib/core/services/adk/acp/acp.py +5 -5
  19. agentex/lib/core/temporal/workers/worker.py +24 -0
  20. agentex/lib/environment_variables.py +2 -0
  21. agentex/lib/sdk/fastacp/base/base_acp_server.py +13 -90
  22. agentex/lib/utils/debug.py +63 -0
  23. agentex/lib/utils/registration.py +101 -0
  24. agentex/resources/agents.py +36 -22
  25. agentex/types/agent.py +7 -0
  26. {agentex_sdk-0.2.6.dist-info → agentex_sdk-0.2.8.dist-info}/METADATA +32 -1
  27. {agentex_sdk-0.2.6.dist-info → agentex_sdk-0.2.8.dist-info}/RECORD +30 -25
  28. {agentex_sdk-0.2.6.dist-info → agentex_sdk-0.2.8.dist-info}/WHEEL +0 -0
  29. {agentex_sdk-0.2.6.dist-info → agentex_sdk-0.2.8.dist-info}/entry_points.txt +0 -0
  30. {agentex_sdk-0.2.6.dist-info → agentex_sdk-0.2.8.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,6 @@ build-backend = "hatchling.build"
6
6
  name = "{{ project_name }}"
7
7
  version = "0.1.0"
8
8
  description = "{{ description }}"
9
- readme = "README.md"
10
9
  requires-python = ">=3.12"
11
10
  dependencies = [
12
11
  "agentex-sdk",
@@ -15,6 +15,8 @@ RUN apt-get update && apt-get install -y \
15
15
  gcc \
16
16
  cmake \
17
17
  netcat-openbsd \
18
+ node \
19
+ npm \
18
20
  && apt-get clean \
19
21
  && rm -rf /var/lib/apt/lists/*
20
22
 
@@ -6,7 +6,6 @@ build-backend = "hatchling.build"
6
6
  name = "{{ project_name }}"
7
7
  version = "0.1.0"
8
8
  description = "{{ description }}"
9
- readme = "README.md"
10
9
  requires-python = ">=3.12"
11
10
  dependencies = [
12
11
  "agentex-sdk",
@@ -15,6 +15,8 @@ RUN apt-get update && apt-get install -y \
15
15
  gcc \
16
16
  cmake \
17
17
  netcat-openbsd \
18
+ node \
19
+ npm \
18
20
  && apt-get clean \
19
21
  && rm -rf /var/lib/apt/lists/*
20
22
 
@@ -1,4 +1,35 @@
1
1
  import os
2
+ import sys
3
+
4
+ # === DEBUG SETUP (AgentEx CLI Debug Support) ===
5
+ if os.getenv("AGENTEX_DEBUG_ENABLED") == "true":
6
+ try:
7
+ import debugpy
8
+ debug_port = int(os.getenv("AGENTEX_DEBUG_PORT", "5679"))
9
+ debug_type = os.getenv("AGENTEX_DEBUG_TYPE", "acp")
10
+ wait_for_attach = os.getenv("AGENTEX_DEBUG_WAIT_FOR_ATTACH", "false").lower() == "true"
11
+
12
+ # Configure debugpy
13
+ debugpy.configure(subProcess=False)
14
+ debugpy.listen(debug_port)
15
+
16
+ print(f"🐛 [{debug_type.upper()}] Debug server listening on port {debug_port}")
17
+
18
+ if wait_for_attach:
19
+ print(f"⏳ [{debug_type.upper()}] Waiting for debugger to attach...")
20
+ debugpy.wait_for_client()
21
+ print(f"✅ [{debug_type.upper()}] Debugger attached!")
22
+ else:
23
+ print(f"📡 [{debug_type.upper()}] Ready for debugger attachment")
24
+
25
+ except ImportError:
26
+ print("❌ debugpy not available. Install with: pip install debugpy")
27
+ sys.exit(1)
28
+ except Exception as e:
29
+ print(f"❌ Debug setup failed: {e}")
30
+ sys.exit(1)
31
+ # === END DEBUG SETUP ===
32
+
2
33
  from agentex.lib.sdk.fastacp.fastacp import FastACP
3
34
  from agentex.lib.types.fastacp import TemporalACPConfig
4
35
 
@@ -1,9 +1,9 @@
1
1
  import asyncio
2
- import os
3
2
 
4
3
  from agentex.lib.core.temporal.activities import get_all_activities
5
4
  from agentex.lib.core.temporal.workers.worker import AgentexWorker
6
5
  from agentex.lib.utils.logging import make_logger
6
+ from agentex.lib.utils.debug import setup_debug_if_enabled
7
7
  from agentex.lib.environment_variables import EnvironmentVariables
8
8
 
9
9
  from workflow import {{ workflow_class }}
@@ -15,6 +15,9 @@ logger = make_logger(__name__)
15
15
 
16
16
 
17
17
  async def main():
18
+ # Setup debug mode if enabled
19
+ setup_debug_if_enabled()
20
+
18
21
  task_queue_name = environment_variables.WORKFLOW_TASK_QUEUE
19
22
  if task_queue_name is None:
20
23
  raise ValueError("WORKFLOW_TASK_QUEUE is not set")
@@ -6,7 +6,6 @@ build-backend = "hatchling.build"
6
6
  name = "{{ project_name }}"
7
7
  version = "0.1.0"
8
8
  description = "{{ description }}"
9
- readme = "README.md"
10
9
  requires-python = ">=3.12"
11
10
  dependencies = [
12
11
  "agentex-sdk",
@@ -20,6 +19,7 @@ dev = [
20
19
  "black",
21
20
  "isort",
22
21
  "flake8",
22
+ "debugpy>=1.8.15",
23
23
  ]
24
24
 
25
25
  [tool.hatch.build.targets.wheel]
@@ -64,7 +64,7 @@ class ACPService:
64
64
  else:
65
65
  raise ValueError("Either agent_name or agent_id must be provided")
66
66
 
67
- task_entry = Task.model_validate(json_rpc_response["result"])
67
+ task_entry = Task.model_validate(json_rpc_response.result)
68
68
  if span:
69
69
  span.output = task_entry.model_dump()
70
70
  return task_entry
@@ -115,7 +115,7 @@ class ACPService:
115
115
  else:
116
116
  raise ValueError("Either agent_name or agent_id must be provided")
117
117
 
118
- task_message = TaskMessage.model_validate(json_rpc_response["result"])
118
+ task_message = TaskMessage.model_validate(json_rpc_response.result)
119
119
  if span:
120
120
  span.output = task_message.model_dump()
121
121
  return task_message
@@ -147,7 +147,7 @@ class ACPService:
147
147
  agent_name=agent_name,
148
148
  method="event/send",
149
149
  params={
150
- "task_name": task_name,
150
+ "task_id": task_id,
151
151
  "content": cast(TaskMessageContentParam, content.model_dump()),
152
152
  },
153
153
  )
@@ -163,7 +163,7 @@ class ACPService:
163
163
  else:
164
164
  raise ValueError("Either agent_name or agent_id must be provided")
165
165
 
166
- event_entry = Event.model_validate(json_rpc_response["result"])
166
+ event_entry = Event.model_validate(json_rpc_response.result)
167
167
  if span:
168
168
  span.output = event_entry.model_dump()
169
169
  return event_entry
@@ -204,7 +204,7 @@ class ACPService:
204
204
  else:
205
205
  raise ValueError("Either task_name or task_id must be provided")
206
206
 
207
- task_entry = Task.model_validate(json_rpc_response["result"])
207
+ task_entry = Task.model_validate(json_rpc_response.result)
208
208
  if span:
209
209
  span.output = task_entry.model_dump()
210
210
  return task_entry
@@ -24,6 +24,8 @@ from temporalio.worker import (
24
24
  )
25
25
 
26
26
  from agentex.lib.utils.logging import make_logger
27
+ from agentex.lib.utils.registration import register_agent
28
+ from agentex.lib.environment_variables import EnvironmentVariables
27
29
 
28
30
  logger = make_logger(__name__)
29
31
 
@@ -103,9 +105,16 @@ class AgentexWorker:
103
105
  workflow: type,
104
106
  ):
105
107
  await self.start_health_check_server()
108
+ await self._register_agent()
106
109
  temporal_client = await get_temporal_client(
107
110
  temporal_address=os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"),
108
111
  )
112
+
113
+ # Enable debug mode if AgentEx debug is enabled (disables deadlock detection)
114
+ debug_enabled = os.environ.get("AGENTEX_DEBUG_ENABLED", "false").lower() == "true"
115
+ if debug_enabled:
116
+ logger.info("🐛 [WORKER] Temporal debug mode enabled - deadlock detection disabled")
117
+
109
118
  worker = Worker(
110
119
  client=temporal_client,
111
120
  task_queue=self.task_queue,
@@ -115,6 +124,7 @@ class AgentexWorker:
115
124
  workflow_runner=UnsandboxedWorkflowRunner(),
116
125
  max_concurrent_activities=self.max_concurrent_activities,
117
126
  build_id=str(uuid.uuid4()),
127
+ debug_mode=debug_enabled, # Disable deadlock detection in debug mode
118
128
  )
119
129
 
120
130
  logger.info(f"Starting workers for task queue: {self.task_queue}")
@@ -160,3 +170,17 @@ class AgentexWorker:
160
170
  f"Failed to start health check server on alternative port {alt_port}: {e}"
161
171
  )
162
172
  raise
173
+
174
+ """
175
+ Register the worker with the Agentex server.
176
+
177
+ Even though the Temporal server will also register the agent with the server,
178
+ doing this on the worker side is required to make sure that both share the API key
179
+ which is returned on registration and used to authenticate the worker with the Agentex server.
180
+ """
181
+ async def _register_agent(self):
182
+ env_vars = EnvironmentVariables.refresh()
183
+ if env_vars and env_vars.AGENTEX_BASE_URL:
184
+ await register_agent(env_vars)
185
+ else:
186
+ logger.warning("AGENTEX_BASE_URL not set, skipping worker registration")
@@ -23,6 +23,7 @@ class EnvVarKeys(str, Enum):
23
23
  AGENT_NAME = "AGENT_NAME"
24
24
  AGENT_DESCRIPTION = "AGENT_DESCRIPTION"
25
25
  AGENT_ID = "AGENT_ID"
26
+ AGENT_API_KEY = "AGENT_API_KEY"
26
27
  # ACP Configuration
27
28
  ACP_URL = "ACP_URL"
28
29
  ACP_PORT = "ACP_PORT"
@@ -52,6 +53,7 @@ class EnvironmentVariables(BaseModel):
52
53
  AGENT_NAME: str
53
54
  AGENT_DESCRIPTION: str | None = None
54
55
  AGENT_ID: str | None = None
56
+ AGENT_API_KEY: str | None = None
55
57
  ACP_TYPE: str | None = "agentic"
56
58
  # ACP Configuration
57
59
  ACP_URL: str
@@ -1,15 +1,10 @@
1
1
  import asyncio
2
- import base64
3
2
  import inspect
4
- import json
5
- import os
6
3
  from collections.abc import AsyncGenerator, Awaitable, Callable
7
4
  from contextlib import asynccontextmanager
8
5
  from typing import Any
9
6
 
10
- import httpx
11
7
  import uvicorn
12
- from agentex.lib.adk.utils._modules.client import create_async_agentex_client
13
8
  from fastapi import FastAPI, Request
14
9
  from fastapi.responses import StreamingResponse
15
10
  from pydantic import TypeAdapter, ValidationError
@@ -30,6 +25,7 @@ from agentex.lib.types.task_message_updates import StreamTaskMessageFull, TaskMe
30
25
  from agentex.types.task_message_content import TaskMessageContent
31
26
  from agentex.lib.utils.logging import make_logger
32
27
  from agentex.lib.utils.model_utils import BaseModel
28
+ from agentex.lib.utils.registration import register_agent
33
29
 
34
30
  logger = make_logger(__name__)
35
31
 
@@ -74,7 +70,7 @@ class BaseACPServer(FastAPI):
74
70
  async def lifespan_context(app: FastAPI):
75
71
  env_vars = EnvironmentVariables.refresh()
76
72
  if env_vars.AGENTEX_BASE_URL:
77
- await self._register_agent(env_vars)
73
+ await register_agent(env_vars)
78
74
  else:
79
75
  logger.warning("AGENTEX_BASE_URL not set, skipping agent registration")
80
76
 
@@ -101,6 +97,16 @@ class BaseACPServer(FastAPI):
101
97
  data = await request.json()
102
98
  rpc_request = JSONRPCRequest(**data)
103
99
 
100
+ # Check if the request is authenticated
101
+ if refreshed_environment_variables and getattr(refreshed_environment_variables, "AGENT_API_KEY", None):
102
+ authorization_header = request.headers.get("x-agent-api-key")
103
+ if authorization_header != refreshed_environment_variables.AGENT_API_KEY:
104
+ return JSONRPCResponse(
105
+ id=rpc_request.id,
106
+ error=JSONRPCError(code=-32601, message="Unauthorized"),
107
+ )
108
+
109
+
104
110
  # Check if method is valid first
105
111
  try:
106
112
  method = RPCMethod(rpc_request.method)
@@ -345,87 +351,4 @@ class BaseACPServer(FastAPI):
345
351
  """Start the Uvicorn server for async handlers."""
346
352
  uvicorn.run(self, host=host, port=port, **kwargs)
347
353
 
348
- def _get_auth_principal(self, env_vars: EnvironmentVariables):
349
- if not env_vars.AUTH_PRINCIPAL_B64:
350
- return None
351
-
352
- try:
353
- decoded_str = base64.b64decode(env_vars.AUTH_PRINCIPAL_B64).decode('utf-8')
354
- return json.loads(decoded_str)
355
- except Exception:
356
- return None
357
-
358
- async def _register_agent(self, env_vars: EnvironmentVariables):
359
- """Register this agent with the Agentex server"""
360
- # Build the agent's own URL
361
- full_acp_url = f"{env_vars.ACP_URL.rstrip('/')}:{env_vars.ACP_PORT}"
362
-
363
- description = (
364
- env_vars.AGENT_DESCRIPTION
365
- or f"Generic description for agent: {env_vars.AGENT_NAME}"
366
- )
367
-
368
- # Prepare registration data
369
- registration_data = {
370
- "name": env_vars.AGENT_NAME,
371
- "description": description,
372
- "acp_url": full_acp_url,
373
- "acp_type": env_vars.ACP_TYPE,
374
- "principal_context": self._get_auth_principal(env_vars)
375
- }
376
-
377
- if env_vars.AGENT_ID:
378
- registration_data["agent_id"] = env_vars.AGENT_ID
379
-
380
- # Make the registration request
381
- registration_url = f"{env_vars.AGENTEX_BASE_URL.rstrip('/')}/agents/register"
382
- # Retry logic with configurable attempts and delay
383
- max_retries = 3
384
- base_delay = 5 # seconds
385
- last_exception = None
386
-
387
- attempt = 0
388
- while attempt < max_retries:
389
- try:
390
- async with httpx.AsyncClient() as client:
391
- response = await client.post(
392
- registration_url, json=registration_data, timeout=30.0
393
- )
394
- if response.status_code == 200:
395
- agent = response.json()
396
- agent_id, agent_name = agent["id"], agent["name"]
397
-
398
- os.environ["AGENT_ID"] = agent_id
399
- os.environ["AGENT_NAME"] = agent_name
400
- env_vars.AGENT_ID = agent_id
401
- env_vars.AGENT_NAME = agent_name
402
- global refreshed_environment_variables
403
- refreshed_environment_variables = env_vars
404
- logger.info(
405
- f"Successfully registered agent '{env_vars.AGENT_NAME}' with Agentex server with acp_url: {full_acp_url}. Registration data: {registration_data}"
406
- )
407
- return # Success, exit the retry loop
408
- else:
409
- error_msg = f"Failed to register agent. Status: {response.status_code}, Response: {response.text}"
410
- logger.error(error_msg)
411
- last_exception = Exception(
412
- f"Failed to startup agent: {response.text}"
413
- )
414
-
415
- except Exception as e:
416
- logger.error(
417
- f"Exception during agent registration attempt {attempt + 1}: {e}"
418
- )
419
- last_exception = e
420
- attempt += 1
421
- if attempt < max_retries:
422
- delay = (attempt) * base_delay # 5, 10, 15 seconds
423
- logger.info(
424
- f"Retrying in {delay} seconds... (attempt {attempt}/{max_retries})"
425
- )
426
- await asyncio.sleep(delay)
427
-
428
- # If we get here, all retries failed
429
- raise last_exception or Exception(
430
- f"Failed to register agent after {max_retries} attempts"
431
- )
354
+
@@ -0,0 +1,63 @@
1
+ """
2
+ Debug utilities for AgentEx development.
3
+
4
+ Provides debugging setup functionality that can be used across different components.
5
+ """
6
+
7
+ import os
8
+ import debugpy # type: ignore
9
+
10
+
11
+ def setup_debug_if_enabled() -> None:
12
+ """
13
+ Setup debugpy if debug mode is enabled via environment variables.
14
+
15
+ This function checks for AgentEx debug environment variables and configures
16
+ debugpy accordingly. It's designed to be called early in worker startup.
17
+
18
+ Environment Variables:
19
+ AGENTEX_DEBUG_ENABLED: Set to "true" to enable debug mode
20
+ AGENTEX_DEBUG_PORT: Port for debug server (default: 5678)
21
+ AGENTEX_DEBUG_TYPE: Type identifier for logging (default: "worker")
22
+ AGENTEX_DEBUG_WAIT_FOR_ATTACH: Set to "true" to wait for debugger attachment
23
+
24
+ Raises:
25
+ Any exception from debugpy setup (will bubble up naturally)
26
+ """
27
+ if os.getenv("AGENTEX_DEBUG_ENABLED") == "true":
28
+ debug_port = int(os.getenv("AGENTEX_DEBUG_PORT", "5678"))
29
+ debug_type = os.getenv("AGENTEX_DEBUG_TYPE", "worker")
30
+ wait_for_attach = os.getenv("AGENTEX_DEBUG_WAIT_FOR_ATTACH", "false").lower() == "true"
31
+
32
+ # Configure debugpy
33
+ debugpy.configure(subProcess=False)
34
+ debugpy.listen(debug_port)
35
+
36
+ print(f"🐛 [{debug_type.upper()}] Debug server listening on port {debug_port}")
37
+
38
+ if wait_for_attach:
39
+ print(f"⏳ [{debug_type.upper()}] Waiting for debugger to attach...")
40
+ debugpy.wait_for_client()
41
+ print(f"✅ [{debug_type.upper()}] Debugger attached!")
42
+ else:
43
+ print(f"📡 [{debug_type.upper()}] Ready for debugger attachment")
44
+
45
+
46
+ def is_debug_enabled() -> bool:
47
+ """
48
+ Check if debug mode is currently enabled.
49
+
50
+ Returns:
51
+ bool: True if AGENTEX_DEBUG_ENABLED is set to "true"
52
+ """
53
+ return os.getenv("AGENTEX_DEBUG_ENABLED", "false").lower() == "true"
54
+
55
+
56
+ def get_debug_port() -> int:
57
+ """
58
+ Get the debug port from environment variables.
59
+
60
+ Returns:
61
+ int: Debug port (default: 5678)
62
+ """
63
+ return int(os.getenv("AGENTEX_DEBUG_PORT", "5678"))
@@ -0,0 +1,101 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ import httpx
5
+ import asyncio
6
+
7
+ from agentex.lib.environment_variables import EnvironmentVariables, refreshed_environment_variables
8
+ from agentex.lib.utils.logging import make_logger
9
+
10
+ logger = make_logger(__name__)
11
+
12
+ def get_auth_principal(env_vars: EnvironmentVariables):
13
+ if not env_vars.AUTH_PRINCIPAL_B64:
14
+ return None
15
+
16
+ try:
17
+ decoded_str = base64.b64decode(env_vars.AUTH_PRINCIPAL_B64).decode('utf-8')
18
+ return json.loads(decoded_str)
19
+ except Exception:
20
+ return None
21
+
22
+ async def register_agent(env_vars: EnvironmentVariables):
23
+ """Register this agent with the Agentex server"""
24
+ if not env_vars.AGENTEX_BASE_URL:
25
+ logger.warning("AGENTEX_BASE_URL is not set, skipping registration")
26
+ return
27
+ # Build the agent's own URL
28
+ full_acp_url = f"{env_vars.ACP_URL.rstrip('/')}:{env_vars.ACP_PORT}"
29
+
30
+ description = (
31
+ env_vars.AGENT_DESCRIPTION
32
+ or f"Generic description for agent: {env_vars.AGENT_NAME}"
33
+ )
34
+
35
+ # Prepare registration data
36
+ registration_data = {
37
+ "name": env_vars.AGENT_NAME,
38
+ "description": description,
39
+ "acp_url": full_acp_url,
40
+ "acp_type": env_vars.ACP_TYPE,
41
+ "principal_context": get_auth_principal(env_vars)
42
+ }
43
+
44
+ if env_vars.AGENT_ID:
45
+ registration_data["agent_id"] = env_vars.AGENT_ID
46
+
47
+ # Make the registration request
48
+ registration_url = f"{env_vars.AGENTEX_BASE_URL.rstrip('/')}/agents/register"
49
+ # Retry logic with configurable attempts and delay
50
+ max_retries = 3
51
+ base_delay = 5 # seconds
52
+ last_exception = None
53
+
54
+ attempt = 0
55
+ while attempt < max_retries:
56
+ try:
57
+ async with httpx.AsyncClient() as client:
58
+ response = await client.post(
59
+ registration_url, json=registration_data, timeout=30.0
60
+ )
61
+ if response.status_code == 200:
62
+ agent = response.json()
63
+ agent_id, agent_name = agent["id"], agent["name"]
64
+ agent_api_key = agent["agent_api_key"]
65
+
66
+ os.environ["AGENT_ID"] = agent_id
67
+ os.environ["AGENT_NAME"] = agent_name
68
+ os.environ["AGENT_API_KEY"] = agent_api_key
69
+ env_vars.AGENT_ID = agent_id
70
+ env_vars.AGENT_NAME = agent_name
71
+ env_vars.AGENT_API_KEY = agent_api_key
72
+ global refreshed_environment_variables
73
+ refreshed_environment_variables = env_vars
74
+ logger.info(
75
+ f"Successfully registered agent '{env_vars.AGENT_NAME}' with Agentex server with acp_url: {full_acp_url}. Registration data: {registration_data}"
76
+ )
77
+ return # Success, exit the retry loop
78
+ else:
79
+ error_msg = f"Failed to register agent. Status: {response.status_code}, Response: {response.text}"
80
+ logger.error(error_msg)
81
+ last_exception = Exception(
82
+ f"Failed to startup agent: {response.text}"
83
+ )
84
+
85
+ except Exception as e:
86
+ logger.error(
87
+ f"Exception during agent registration attempt {attempt + 1}: {e}"
88
+ )
89
+ last_exception = e
90
+ attempt += 1
91
+ if attempt < max_retries:
92
+ delay = (attempt) * base_delay # 5, 10, 15 seconds
93
+ logger.info(
94
+ f"Retrying in {delay} seconds... (attempt {attempt}/{max_retries})"
95
+ )
96
+ await asyncio.sleep(delay)
97
+
98
+ # If we get here, all retries failed
99
+ raise last_exception or Exception(
100
+ f"Failed to register agent after {max_retries} attempts"
101
+ )
@@ -508,17 +508,24 @@ class AgentsResource(SyncAPIResource):
508
508
  raise ValueError("Either agent_id or agent_name must be provided")
509
509
 
510
510
  with raw_agent_rpc_response as response:
511
- for agent_rpc_response_str in response.iter_text():
512
- if agent_rpc_response_str.strip(): # Only process non-empty lines
513
- try:
514
- chunk_rpc_response = SendMessageStreamResponse.model_validate(
515
- json.loads(agent_rpc_response_str),
516
- from_attributes=True
517
- )
518
- yield chunk_rpc_response
519
- except json.JSONDecodeError:
520
- # Skip invalid JSON lines
521
- continue
511
+ for _line in response.iter_lines():
512
+ if not _line:
513
+ continue
514
+ line = _line.strip()
515
+ # Handle optional SSE-style prefix
516
+ if line.startswith("data:"):
517
+ line = line[len("data:"):].strip()
518
+ if not line:
519
+ continue
520
+ try:
521
+ chunk_rpc_response = SendMessageStreamResponse.model_validate(
522
+ json.loads(line),
523
+ from_attributes=True
524
+ )
525
+ yield chunk_rpc_response
526
+ except json.JSONDecodeError:
527
+ # Skip invalid JSON lines
528
+ continue
522
529
 
523
530
  def send_event(
524
531
  self,
@@ -1048,17 +1055,24 @@ class AsyncAgentsResource(AsyncAPIResource):
1048
1055
  raise ValueError("Either agent_id or agent_name must be provided")
1049
1056
 
1050
1057
  async with raw_agent_rpc_response as response:
1051
- async for agent_rpc_response_str in response.iter_text():
1052
- if agent_rpc_response_str.strip(): # Only process non-empty lines
1053
- try:
1054
- chunk_rpc_response = SendMessageStreamResponse.model_validate(
1055
- json.loads(agent_rpc_response_str),
1056
- from_attributes=True
1057
- )
1058
- yield chunk_rpc_response
1059
- except json.JSONDecodeError:
1060
- # Skip invalid JSON lines
1061
- continue
1058
+ async for _line in response.iter_lines():
1059
+ if not _line:
1060
+ continue
1061
+ line = _line.strip()
1062
+ # Handle optional SSE-style prefix
1063
+ if line.startswith("data:"):
1064
+ line = line[len("data:"):].strip()
1065
+ if not line:
1066
+ continue
1067
+ try:
1068
+ chunk_rpc_response = SendMessageStreamResponse.model_validate(
1069
+ json.loads(line),
1070
+ from_attributes=True
1071
+ )
1072
+ yield chunk_rpc_response
1073
+ except json.JSONDecodeError:
1074
+ # Skip invalid JSON lines
1075
+ continue
1062
1076
 
1063
1077
  async def send_event(
1064
1078
  self,
agentex/types/agent.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  from typing import Optional
4
+ from datetime import datetime
4
5
  from typing_extensions import Literal
5
6
 
6
7
  from .._models import BaseModel
@@ -16,12 +17,18 @@ class Agent(BaseModel):
16
17
  acp_type: AcpType
17
18
  """The type of the ACP Server (Either sync or agentic)"""
18
19
 
20
+ created_at: datetime
21
+ """The timestamp when the agent was created"""
22
+
19
23
  description: str
20
24
  """The description of the action."""
21
25
 
22
26
  name: str
23
27
  """The unique name of the agent."""
24
28
 
29
+ updated_at: datetime
30
+ """The timestamp when the agent was last updated"""
31
+
25
32
  status: Optional[Literal["Pending", "Building", "Ready", "Failed", "Unknown"]] = None
26
33
  """The status of the action, indicating if it's building, ready, failed, etc."""
27
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentex-sdk
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: The official Python library for the agentex API
5
5
  Project-URL: Homepage, https://github.com/scaleapi/agentex-python
6
6
  Project-URL: Repository, https://github.com/scaleapi/agentex-python
@@ -126,6 +126,37 @@ asyncio.run(main())
126
126
 
127
127
  Functionality between the synchronous and asynchronous clients is otherwise identical.
128
128
 
129
+ ## Debugging
130
+
131
+ AgentEx provides built-in debugging support for **temporal projects** during local development.
132
+
133
+ ```bash
134
+ # Basic debugging
135
+ uv run agentex agents run --manifest manifest.yaml --debug-worker
136
+
137
+ # Wait for debugger to attach before starting
138
+ uv run agentex agents run --manifest manifest.yaml --debug-worker --wait-for-debugger
139
+
140
+ # Custom debug port
141
+ uv run agentex agents run --manifest manifest.yaml --debug-worker --debug-port 5679
142
+ ```
143
+
144
+ For **VS Code**, add this configuration to `.vscode/launch.json`:
145
+
146
+ ```json
147
+ {
148
+ "name": "Attach to AgentEx Worker",
149
+ "type": "debugpy",
150
+ "request": "attach",
151
+ "connect": { "host": "localhost", "port": 5678 },
152
+ "pathMappings": [{ "localRoot": "${workspaceFolder}", "remoteRoot": "." }],
153
+ "justMyCode": false,
154
+ "console": "integratedTerminal"
155
+ }
156
+ ```
157
+
158
+ The debug server automatically finds an available port starting from 5678 and prints connection details when starting.
159
+
129
160
  ### With aiohttp
130
161
 
131
162
  By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.