agentex-sdk 0.1.0a6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. agentex/__init__.py +103 -0
  2. agentex/_base_client.py +1992 -0
  3. agentex/_client.py +506 -0
  4. agentex/_compat.py +219 -0
  5. agentex/_constants.py +14 -0
  6. agentex/_exceptions.py +108 -0
  7. agentex/_files.py +123 -0
  8. agentex/_models.py +829 -0
  9. agentex/_qs.py +150 -0
  10. agentex/_resource.py +43 -0
  11. agentex/_response.py +830 -0
  12. agentex/_streaming.py +333 -0
  13. agentex/_types.py +219 -0
  14. agentex/_utils/__init__.py +57 -0
  15. agentex/_utils/_logs.py +25 -0
  16. agentex/_utils/_proxy.py +65 -0
  17. agentex/_utils/_reflection.py +42 -0
  18. agentex/_utils/_resources_proxy.py +24 -0
  19. agentex/_utils/_streams.py +12 -0
  20. agentex/_utils/_sync.py +86 -0
  21. agentex/_utils/_transform.py +447 -0
  22. agentex/_utils/_typing.py +151 -0
  23. agentex/_utils/_utils.py +422 -0
  24. agentex/_version.py +4 -0
  25. agentex/lib/.keep +4 -0
  26. agentex/lib/__init__.py +0 -0
  27. agentex/lib/adk/__init__.py +41 -0
  28. agentex/lib/adk/_modules/__init__.py +0 -0
  29. agentex/lib/adk/_modules/acp.py +247 -0
  30. agentex/lib/adk/_modules/agent_task_tracker.py +176 -0
  31. agentex/lib/adk/_modules/agents.py +77 -0
  32. agentex/lib/adk/_modules/events.py +141 -0
  33. agentex/lib/adk/_modules/messages.py +285 -0
  34. agentex/lib/adk/_modules/state.py +291 -0
  35. agentex/lib/adk/_modules/streaming.py +75 -0
  36. agentex/lib/adk/_modules/tasks.py +124 -0
  37. agentex/lib/adk/_modules/tracing.py +194 -0
  38. agentex/lib/adk/providers/__init__.py +9 -0
  39. agentex/lib/adk/providers/_modules/__init__.py +0 -0
  40. agentex/lib/adk/providers/_modules/litellm.py +232 -0
  41. agentex/lib/adk/providers/_modules/openai.py +416 -0
  42. agentex/lib/adk/providers/_modules/sgp.py +85 -0
  43. agentex/lib/adk/utils/__init__.py +5 -0
  44. agentex/lib/adk/utils/_modules/__init__.py +0 -0
  45. agentex/lib/adk/utils/_modules/templating.py +94 -0
  46. agentex/lib/cli/__init__.py +0 -0
  47. agentex/lib/cli/commands/__init__.py +0 -0
  48. agentex/lib/cli/commands/agents.py +328 -0
  49. agentex/lib/cli/commands/init.py +227 -0
  50. agentex/lib/cli/commands/main.py +33 -0
  51. agentex/lib/cli/commands/secrets.py +169 -0
  52. agentex/lib/cli/commands/tasks.py +118 -0
  53. agentex/lib/cli/commands/uv.py +133 -0
  54. agentex/lib/cli/handlers/__init__.py +0 -0
  55. agentex/lib/cli/handlers/agent_handlers.py +160 -0
  56. agentex/lib/cli/handlers/cleanup_handlers.py +186 -0
  57. agentex/lib/cli/handlers/deploy_handlers.py +351 -0
  58. agentex/lib/cli/handlers/run_handlers.py +452 -0
  59. agentex/lib/cli/handlers/secret_handlers.py +670 -0
  60. agentex/lib/cli/templates/default/.dockerignore.j2 +43 -0
  61. agentex/lib/cli/templates/default/Dockerfile-uv.j2 +42 -0
  62. agentex/lib/cli/templates/default/Dockerfile.j2 +42 -0
  63. agentex/lib/cli/templates/default/README.md.j2 +193 -0
  64. agentex/lib/cli/templates/default/deploy/example.yaml.j2 +55 -0
  65. agentex/lib/cli/templates/default/manifest.yaml.j2 +116 -0
  66. agentex/lib/cli/templates/default/project/acp.py.j2 +29 -0
  67. agentex/lib/cli/templates/default/pyproject.toml.j2 +33 -0
  68. agentex/lib/cli/templates/default/requirements.txt.j2 +5 -0
  69. agentex/lib/cli/templates/deploy/Screenshot 2025-03-19 at 10.36.57/342/200/257AM.png +0 -0
  70. agentex/lib/cli/templates/deploy/example.yaml.j2 +55 -0
  71. agentex/lib/cli/templates/sync/.dockerignore.j2 +43 -0
  72. agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +42 -0
  73. agentex/lib/cli/templates/sync/Dockerfile.j2 +42 -0
  74. agentex/lib/cli/templates/sync/README.md.j2 +293 -0
  75. agentex/lib/cli/templates/sync/deploy/example.yaml.j2 +55 -0
  76. agentex/lib/cli/templates/sync/manifest.yaml.j2 +116 -0
  77. agentex/lib/cli/templates/sync/project/acp.py.j2 +26 -0
  78. agentex/lib/cli/templates/sync/pyproject.toml.j2 +33 -0
  79. agentex/lib/cli/templates/sync/requirements.txt.j2 +5 -0
  80. agentex/lib/cli/templates/temporal/.dockerignore.j2 +43 -0
  81. agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +48 -0
  82. agentex/lib/cli/templates/temporal/Dockerfile.j2 +48 -0
  83. agentex/lib/cli/templates/temporal/README.md.j2 +316 -0
  84. agentex/lib/cli/templates/temporal/deploy/example.yaml.j2 +55 -0
  85. agentex/lib/cli/templates/temporal/manifest.yaml.j2 +137 -0
  86. agentex/lib/cli/templates/temporal/project/acp.py.j2 +30 -0
  87. agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +33 -0
  88. agentex/lib/cli/templates/temporal/project/workflow.py.j2 +66 -0
  89. agentex/lib/cli/templates/temporal/pyproject.toml.j2 +34 -0
  90. agentex/lib/cli/templates/temporal/requirements.txt.j2 +5 -0
  91. agentex/lib/cli/utils/cli_utils.py +14 -0
  92. agentex/lib/cli/utils/credential_utils.py +103 -0
  93. agentex/lib/cli/utils/exceptions.py +6 -0
  94. agentex/lib/cli/utils/kubectl_utils.py +135 -0
  95. agentex/lib/cli/utils/kubernetes_secrets_utils.py +185 -0
  96. agentex/lib/core/__init__.py +0 -0
  97. agentex/lib/core/adapters/__init__.py +0 -0
  98. agentex/lib/core/adapters/llm/__init__.py +1 -0
  99. agentex/lib/core/adapters/llm/adapter_litellm.py +46 -0
  100. agentex/lib/core/adapters/llm/adapter_sgp.py +55 -0
  101. agentex/lib/core/adapters/llm/port.py +24 -0
  102. agentex/lib/core/adapters/streams/adapter_redis.py +128 -0
  103. agentex/lib/core/adapters/streams/port.py +50 -0
  104. agentex/lib/core/clients/__init__.py +1 -0
  105. agentex/lib/core/clients/temporal/__init__.py +0 -0
  106. agentex/lib/core/clients/temporal/temporal_client.py +181 -0
  107. agentex/lib/core/clients/temporal/types.py +47 -0
  108. agentex/lib/core/clients/temporal/utils.py +56 -0
  109. agentex/lib/core/services/__init__.py +0 -0
  110. agentex/lib/core/services/adk/__init__.py +0 -0
  111. agentex/lib/core/services/adk/acp/__init__.py +0 -0
  112. agentex/lib/core/services/adk/acp/acp.py +210 -0
  113. agentex/lib/core/services/adk/agent_task_tracker.py +85 -0
  114. agentex/lib/core/services/adk/agents.py +43 -0
  115. agentex/lib/core/services/adk/events.py +61 -0
  116. agentex/lib/core/services/adk/messages.py +164 -0
  117. agentex/lib/core/services/adk/providers/__init__.py +0 -0
  118. agentex/lib/core/services/adk/providers/litellm.py +256 -0
  119. agentex/lib/core/services/adk/providers/openai.py +723 -0
  120. agentex/lib/core/services/adk/providers/sgp.py +99 -0
  121. agentex/lib/core/services/adk/state.py +120 -0
  122. agentex/lib/core/services/adk/streaming.py +262 -0
  123. agentex/lib/core/services/adk/tasks.py +69 -0
  124. agentex/lib/core/services/adk/tracing.py +36 -0
  125. agentex/lib/core/services/adk/utils/__init__.py +0 -0
  126. agentex/lib/core/services/adk/utils/templating.py +58 -0
  127. agentex/lib/core/temporal/__init__.py +0 -0
  128. agentex/lib/core/temporal/activities/__init__.py +207 -0
  129. agentex/lib/core/temporal/activities/activity_helpers.py +37 -0
  130. agentex/lib/core/temporal/activities/adk/__init__.py +0 -0
  131. agentex/lib/core/temporal/activities/adk/acp/__init__.py +0 -0
  132. agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +86 -0
  133. agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +76 -0
  134. agentex/lib/core/temporal/activities/adk/agents_activities.py +35 -0
  135. agentex/lib/core/temporal/activities/adk/events_activities.py +50 -0
  136. agentex/lib/core/temporal/activities/adk/messages_activities.py +94 -0
  137. agentex/lib/core/temporal/activities/adk/providers/__init__.py +0 -0
  138. agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +71 -0
  139. agentex/lib/core/temporal/activities/adk/providers/openai_activities.py +210 -0
  140. agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +42 -0
  141. agentex/lib/core/temporal/activities/adk/state_activities.py +85 -0
  142. agentex/lib/core/temporal/activities/adk/streaming_activities.py +33 -0
  143. agentex/lib/core/temporal/activities/adk/tasks_activities.py +48 -0
  144. agentex/lib/core/temporal/activities/adk/tracing_activities.py +55 -0
  145. agentex/lib/core/temporal/activities/adk/utils/__init__.py +0 -0
  146. agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +41 -0
  147. agentex/lib/core/temporal/services/__init__.py +0 -0
  148. agentex/lib/core/temporal/services/temporal_task_service.py +69 -0
  149. agentex/lib/core/temporal/types/__init__.py +0 -0
  150. agentex/lib/core/temporal/types/workflow.py +5 -0
  151. agentex/lib/core/temporal/workers/__init__.py +0 -0
  152. agentex/lib/core/temporal/workers/worker.py +162 -0
  153. agentex/lib/core/temporal/workflows/workflow.py +26 -0
  154. agentex/lib/core/tracing/__init__.py +5 -0
  155. agentex/lib/core/tracing/processors/agentex_tracing_processor.py +117 -0
  156. agentex/lib/core/tracing/processors/sgp_tracing_processor.py +119 -0
  157. agentex/lib/core/tracing/processors/tracing_processor_interface.py +40 -0
  158. agentex/lib/core/tracing/trace.py +311 -0
  159. agentex/lib/core/tracing/tracer.py +70 -0
  160. agentex/lib/core/tracing/tracing_processor_manager.py +62 -0
  161. agentex/lib/environment_variables.py +87 -0
  162. agentex/lib/py.typed +0 -0
  163. agentex/lib/sdk/__init__.py +0 -0
  164. agentex/lib/sdk/config/__init__.py +0 -0
  165. agentex/lib/sdk/config/agent_config.py +61 -0
  166. agentex/lib/sdk/config/agent_manifest.py +219 -0
  167. agentex/lib/sdk/config/build_config.py +35 -0
  168. agentex/lib/sdk/config/deployment_config.py +117 -0
  169. agentex/lib/sdk/config/local_development_config.py +56 -0
  170. agentex/lib/sdk/config/project_config.py +103 -0
  171. agentex/lib/sdk/fastacp/__init__.py +3 -0
  172. agentex/lib/sdk/fastacp/base/base_acp_server.py +406 -0
  173. agentex/lib/sdk/fastacp/fastacp.py +74 -0
  174. agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +72 -0
  175. agentex/lib/sdk/fastacp/impl/sync_acp.py +109 -0
  176. agentex/lib/sdk/fastacp/impl/temporal_acp.py +97 -0
  177. agentex/lib/sdk/fastacp/tests/README.md +297 -0
  178. agentex/lib/sdk/fastacp/tests/conftest.py +307 -0
  179. agentex/lib/sdk/fastacp/tests/pytest.ini +10 -0
  180. agentex/lib/sdk/fastacp/tests/run_tests.py +227 -0
  181. agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +450 -0
  182. agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +344 -0
  183. agentex/lib/sdk/fastacp/tests/test_integration.py +477 -0
  184. agentex/lib/sdk/state_machine/__init__.py +6 -0
  185. agentex/lib/sdk/state_machine/noop_workflow.py +21 -0
  186. agentex/lib/sdk/state_machine/state.py +10 -0
  187. agentex/lib/sdk/state_machine/state_machine.py +189 -0
  188. agentex/lib/sdk/state_machine/state_workflow.py +16 -0
  189. agentex/lib/sdk/utils/__init__.py +0 -0
  190. agentex/lib/sdk/utils/messages.py +223 -0
  191. agentex/lib/types/__init__.py +0 -0
  192. agentex/lib/types/acp.py +94 -0
  193. agentex/lib/types/agent_configs.py +79 -0
  194. agentex/lib/types/agent_results.py +29 -0
  195. agentex/lib/types/credentials.py +34 -0
  196. agentex/lib/types/fastacp.py +61 -0
  197. agentex/lib/types/files.py +13 -0
  198. agentex/lib/types/json_rpc.py +49 -0
  199. agentex/lib/types/llm_messages.py +354 -0
  200. agentex/lib/types/task_message_updates.py +171 -0
  201. agentex/lib/types/tracing.py +34 -0
  202. agentex/lib/utils/__init__.py +0 -0
  203. agentex/lib/utils/completions.py +131 -0
  204. agentex/lib/utils/console.py +14 -0
  205. agentex/lib/utils/io.py +29 -0
  206. agentex/lib/utils/iterables.py +14 -0
  207. agentex/lib/utils/json_schema.py +23 -0
  208. agentex/lib/utils/logging.py +31 -0
  209. agentex/lib/utils/mcp.py +17 -0
  210. agentex/lib/utils/model_utils.py +46 -0
  211. agentex/lib/utils/parsing.py +15 -0
  212. agentex/lib/utils/regex.py +6 -0
  213. agentex/lib/utils/temporal.py +13 -0
  214. agentex/py.typed +0 -0
  215. agentex/resources/__init__.py +103 -0
  216. agentex/resources/agents.py +707 -0
  217. agentex/resources/events.py +294 -0
  218. agentex/resources/messages/__init__.py +33 -0
  219. agentex/resources/messages/batch.py +271 -0
  220. agentex/resources/messages/messages.py +492 -0
  221. agentex/resources/spans.py +557 -0
  222. agentex/resources/states.py +544 -0
  223. agentex/resources/tasks.py +615 -0
  224. agentex/resources/tracker.py +384 -0
  225. agentex/types/__init__.py +56 -0
  226. agentex/types/acp_type.py +7 -0
  227. agentex/types/agent.py +29 -0
  228. agentex/types/agent_list_params.py +13 -0
  229. agentex/types/agent_list_response.py +10 -0
  230. agentex/types/agent_rpc_by_name_params.py +21 -0
  231. agentex/types/agent_rpc_params.py +51 -0
  232. agentex/types/agent_rpc_params1.py +21 -0
  233. agentex/types/agent_rpc_response.py +20 -0
  234. agentex/types/agent_rpc_result.py +90 -0
  235. agentex/types/agent_task_tracker.py +34 -0
  236. agentex/types/data_content.py +30 -0
  237. agentex/types/data_content_param.py +31 -0
  238. agentex/types/data_delta.py +14 -0
  239. agentex/types/event.py +29 -0
  240. agentex/types/event_list_params.py +22 -0
  241. agentex/types/event_list_response.py +10 -0
  242. agentex/types/message_author.py +7 -0
  243. agentex/types/message_create_params.py +18 -0
  244. agentex/types/message_list_params.py +14 -0
  245. agentex/types/message_list_response.py +10 -0
  246. agentex/types/message_style.py +7 -0
  247. agentex/types/message_update_params.py +18 -0
  248. agentex/types/messages/__init__.py +8 -0
  249. agentex/types/messages/batch_create_params.py +16 -0
  250. agentex/types/messages/batch_create_response.py +10 -0
  251. agentex/types/messages/batch_update_params.py +16 -0
  252. agentex/types/messages/batch_update_response.py +10 -0
  253. agentex/types/shared/__init__.py +3 -0
  254. agentex/types/shared/task_message_update.py +83 -0
  255. agentex/types/span.py +36 -0
  256. agentex/types/span_create_params.py +40 -0
  257. agentex/types/span_list_params.py +12 -0
  258. agentex/types/span_list_response.py +10 -0
  259. agentex/types/span_update_params.py +37 -0
  260. agentex/types/state.py +25 -0
  261. agentex/types/state_create_params.py +16 -0
  262. agentex/types/state_list_params.py +16 -0
  263. agentex/types/state_list_response.py +10 -0
  264. agentex/types/state_update_params.py +16 -0
  265. agentex/types/task.py +23 -0
  266. agentex/types/task_delete_by_name_response.py +8 -0
  267. agentex/types/task_delete_response.py +8 -0
  268. agentex/types/task_list_response.py +10 -0
  269. agentex/types/task_message.py +33 -0
  270. agentex/types/task_message_content.py +16 -0
  271. agentex/types/task_message_content_param.py +17 -0
  272. agentex/types/task_message_delta.py +16 -0
  273. agentex/types/text_content.py +53 -0
  274. agentex/types/text_content_param.py +54 -0
  275. agentex/types/text_delta.py +14 -0
  276. agentex/types/tool_request_content.py +36 -0
  277. agentex/types/tool_request_content_param.py +37 -0
  278. agentex/types/tool_request_delta.py +18 -0
  279. agentex/types/tool_response_content.py +36 -0
  280. agentex/types/tool_response_content_param.py +36 -0
  281. agentex/types/tool_response_delta.py +18 -0
  282. agentex/types/tracker_list_params.py +16 -0
  283. agentex/types/tracker_list_response.py +10 -0
  284. agentex/types/tracker_update_params.py +19 -0
  285. agentex_sdk-0.1.0a6.dist-info/METADATA +426 -0
  286. agentex_sdk-0.1.0a6.dist-info/RECORD +289 -0
  287. agentex_sdk-0.1.0a6.dist-info/WHEEL +4 -0
  288. agentex_sdk-0.1.0a6.dist-info/entry_points.txt +2 -0
  289. agentex_sdk-0.1.0a6.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,97 @@
1
+ from contextlib import asynccontextmanager
2
+ from typing import AsyncGenerator, Callable
3
+
4
+ from fastapi import FastAPI
5
+
6
+ from agentex.lib.core.clients.temporal.temporal_client import TemporalClient
7
+ from agentex.lib.core.temporal.services.temporal_task_service import TemporalTaskService
8
+ from agentex.lib.environment_variables import EnvironmentVariables
9
+ from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer
10
+ from agentex.lib.types.acp import (
11
+ CancelTaskParams,
12
+ CreateTaskParams,
13
+ SendEventParams,
14
+ )
15
+ from agentex.lib.utils.logging import make_logger
16
+
17
+ logger = make_logger(__name__)
18
+
19
+
20
+ class TemporalACP(BaseACPServer):
21
+ """
22
+ Temporal-specific implementation of AsyncAgentACP.
23
+ Uses TaskService to forward operations to temporal workflows.
24
+ """
25
+
26
+ def __init__(
27
+ self, temporal_address: str, temporal_task_service: TemporalTaskService | None = None
28
+ ):
29
+ super().__init__()
30
+ self._temporal_task_service = temporal_task_service
31
+ self._temporal_address = temporal_address
32
+
33
+ @classmethod
34
+ def create(cls, temporal_address: str) -> "TemporalACP":
35
+ logger.info("Initializing TemporalACP instance")
36
+
37
+ # Create instance without temporal client initially
38
+ temporal_acp = cls(temporal_address=temporal_address)
39
+ temporal_acp._setup_handlers()
40
+ logger.info("TemporalACP instance initialized now")
41
+ return temporal_acp
42
+
43
+ # This is to override the lifespan function of the base
44
+ def get_lifespan_function(self) -> Callable[[FastAPI], AsyncGenerator[None, None]]:
45
+ @asynccontextmanager
46
+ async def lifespan(app: FastAPI):
47
+ # Create temporal client during startup
48
+ if self._temporal_address is None:
49
+ raise ValueError("Temporal address is not set")
50
+
51
+ if self._temporal_task_service is None:
52
+ env_vars = EnvironmentVariables.refresh()
53
+ temporal_client = await TemporalClient.create(
54
+ temporal_address=self._temporal_address
55
+ )
56
+ self._temporal_task_service = TemporalTaskService(
57
+ temporal_client=temporal_client,
58
+ env_vars=env_vars,
59
+ )
60
+
61
+ # Call parent lifespan for agent registration
62
+ async with super().get_lifespan_function()(app):
63
+ yield
64
+
65
+ return lifespan
66
+
67
+ def _setup_handlers(self):
68
+ """Set up the handlers for temporal workflow operations"""
69
+
70
+ @self.on_task_create
71
+ async def handle_task_create(params: CreateTaskParams) -> None:
72
+ """Default create task handler - logs the task"""
73
+ logger.info(f"TemporalACP received task create rpc call for task {params.task.id}")
74
+ await self._temporal_task_service.submit_task(agent=params.agent, task=params.task)
75
+
76
+ @self.on_task_event_send
77
+ async def handle_event_send(params: SendEventParams) -> None:
78
+ """Forward messages to running workflows via TaskService"""
79
+ try:
80
+ await self._temporal_task_service.send_event(
81
+ agent=params.agent,
82
+ task=params.task,
83
+ event=params.event,
84
+ )
85
+
86
+ except Exception as e:
87
+ logger.error(f"Failed to send message: {e}")
88
+ raise
89
+
90
+ @self.on_task_cancel
91
+ async def handle_cancel(params: CancelTaskParams) -> None:
92
+ """Cancel running workflows via TaskService"""
93
+ try:
94
+ await self._temporal_task_service.cancel(task_id=params.task.id)
95
+ except Exception as e:
96
+ logger.error(f"Failed to cancel task: {e}")
97
+ raise
@@ -0,0 +1,297 @@
1
+ # BaseACPServer Test Suite
2
+
3
+ This directory contains comprehensive tests for the `BaseACPServer` and its implementations (`SyncACP`, `AgenticBaseACP`, and `TemporalACP`).
4
+
5
+ ## Test Structure
6
+
7
+ The test suite is organized into several categories:
8
+
9
+ ### 1. Core Unit Tests (`test_base_acp_server.py`)
10
+ - **TestBaseACPServerInitialization**: Server initialization and setup
11
+ - **TestHealthCheckEndpoint**: Health check endpoint functionality
12
+ - **TestJSONRPCEndpointCore**: Basic JSON-RPC endpoint functionality
13
+ - **TestHandlerRegistration**: Handler registration and management
14
+ - **TestBackgroundProcessing**: Background task processing
15
+ - **TestErrorHandling**: Basic error handling scenarios
16
+
17
+ ### 2. JSON-RPC Endpoint Tests (`test_json_rpc_endpoints.py`)
18
+ - **TestJSONRPCMethodHandling**: Method routing and execution
19
+ - **TestJSONRPCParameterValidation**: Parameter parsing and validation
20
+ - **TestJSONRPCResponseFormat**: Response formatting compliance
21
+ - **TestJSONRPCErrorCodes**: JSON-RPC 2.0 error code compliance
22
+ - **TestJSONRPCConcurrency**: Concurrent request handling
23
+
24
+ ### 3. Integration Tests (`test_server_integration.py`)
25
+ - **TestServerLifecycle**: Server startup, running, and shutdown
26
+ - **TestHTTPClientIntegration**: Real HTTP client interactions
27
+ - **TestHandlerExecutionIntegration**: Handler execution in server environment
28
+ - **TestServerPerformance**: Performance characteristics
29
+
30
+ ### 4. Implementation Tests (`test_implementations.py`)
31
+ - **TestSyncACP**: SyncACP-specific functionality
32
+ - **TestAgenticBaseACP**: AgenticBaseACP-specific functionality
33
+ - **TestTemporalACP**: TemporalACP-specific functionality
34
+ - **TestImplementationComparison**: Differences between implementations
35
+ - **TestImplementationErrorHandling**: Implementation-specific error handling
36
+
37
+ ### 5. Error Handling Tests (`test_error_handling.py`)
38
+ - **TestMalformedRequestHandling**: Invalid and malformed requests
39
+ - **TestHandlerErrorHandling**: Handler-level error scenarios
40
+ - **TestServerErrorHandling**: Server-level error handling
41
+ - **TestEdgeCases**: Edge cases and boundary conditions
42
+
43
+ ## Running Tests
44
+
45
+ ### Prerequisites
46
+
47
+ Install test dependencies:
48
+ ```bash
49
+ pip install pytest pytest-asyncio httpx pytest-cov pytest-xdist
50
+ ```
51
+
52
+ ### Basic Usage
53
+
54
+ Run all tests:
55
+ ```bash
56
+ python run_tests.py
57
+ ```
58
+
59
+ Run specific test categories:
60
+ ```bash
61
+ python run_tests.py --category unit
62
+ python run_tests.py --category integration
63
+ python run_tests.py --category implementations
64
+ python run_tests.py --category error
65
+ ```
66
+
67
+ ### Advanced Options
68
+
69
+ Run with coverage:
70
+ ```bash
71
+ python run_tests.py --coverage
72
+ ```
73
+
74
+ Run in parallel:
75
+ ```bash
76
+ python run_tests.py --parallel 4
77
+ ```
78
+
79
+ Run with increased verbosity:
80
+ ```bash
81
+ python run_tests.py -vv
82
+ ```
83
+
84
+ Stop on first failure:
85
+ ```bash
86
+ python run_tests.py --failfast
87
+ ```
88
+
89
+ Run only failed tests from last run:
90
+ ```bash
91
+ python run_tests.py --lf
92
+ ```
93
+
94
+ ### Quick Test Options
95
+
96
+ For development, use these quick test commands:
97
+
98
+ ```bash
99
+ # Quick smoke tests
100
+ python run_tests.py smoke
101
+
102
+ # Quick development tests
103
+ python run_tests.py quick
104
+
105
+ # Performance tests only
106
+ python run_tests.py perf
107
+ ```
108
+
109
+ ### Direct pytest Usage
110
+
111
+ You can also run tests directly with pytest:
112
+
113
+ ```bash
114
+ # Run all tests
115
+ pytest
116
+
117
+ # Run specific test file
118
+ pytest test_base_acp_server.py
119
+
120
+ # Run specific test class
121
+ pytest test_base_acp_server.py::TestBaseACPServerInitialization
122
+
123
+ # Run specific test method
124
+ pytest test_base_acp_server.py::TestBaseACPServerInitialization::test_base_acp_server_init
125
+
126
+ # Run with markers
127
+ pytest -m "not slow"
128
+ ```
129
+
130
+ ## Test Configuration
131
+
132
+ ### Fixtures (`conftest.py`)
133
+
134
+ The test suite uses several fixtures:
135
+
136
+ - **`free_port`**: Provides a free port for testing
137
+ - **`sample_task`**, **`sample_message`**: Sample data objects
138
+ - **`base_acp_server`**, **`sync_acp`**, **`agentic_base_acp`**, **`mock_temporal_acp`**: Server instances
139
+ - **`test_server_runner`**: Manages server lifecycle for integration tests
140
+ - **`jsonrpc_client_factory`**: Creates JSON-RPC test clients
141
+ - **`mock_env_vars`**: Mocked environment variables
142
+
143
+ ### Test Utilities
144
+
145
+ - **`TestServerRunner`**: Manages server startup/shutdown for integration tests
146
+ - **`JSONRPCTestClient`**: Simplified JSON-RPC client for testing
147
+ - **`find_free_port()`**: Utility to find available ports
148
+
149
+ ## Test Categories Explained
150
+
151
+ ### Unit Tests
152
+ Focus on individual components in isolation:
153
+ - Server initialization
154
+ - Handler registration
155
+ - Basic endpoint functionality
156
+ - Parameter validation
157
+
158
+ ### Integration Tests
159
+ Test components working together:
160
+ - Full server lifecycle
161
+ - Real HTTP requests
162
+ - Handler execution in server context
163
+ - Performance characteristics
164
+
165
+ ### Implementation Tests
166
+ Test specific ACP implementations:
167
+ - SyncACP behavior
168
+ - AgenticBaseACP send_event functionality
169
+ - TemporalACP workflow integration
170
+ - Implementation differences
171
+
172
+ ### Error Handling Tests
173
+ Comprehensive error scenarios:
174
+ - Malformed JSON-RPC requests
175
+ - Handler exceptions
176
+ - Server error recovery
177
+ - Edge cases and boundary conditions
178
+
179
+ ## Writing New Tests
180
+
181
+ ### Test Naming Convention
182
+ - Test files: `test_*.py`
183
+ - Test classes: `Test*`
184
+ - Test methods: `test_*`
185
+
186
+ ### Async Test Example
187
+ ```python
188
+ @pytest.mark.asyncio
189
+ async def test_my_async_functionality(self, base_acp_server):
190
+ # Your async test code here
191
+ result = await some_async_operation()
192
+ assert result is not None
193
+ ```
194
+
195
+ ### Integration Test Example
196
+ ```python
197
+ @pytest.mark.asyncio
198
+ async def test_server_integration(self, base_acp_server, free_port, test_server_runner):
199
+ runner = test_server_runner(base_acp_server, free_port)
200
+ await runner.start()
201
+
202
+ try:
203
+ # Test server functionality
204
+ async with httpx.AsyncClient() as client:
205
+ response = await client.get(f"http://127.0.0.1:{free_port}/healthz")
206
+ assert response.status_code == 200
207
+ finally:
208
+ await runner.stop()
209
+ ```
210
+
211
+ ### Handler Test Example
212
+ ```python
213
+ @pytest.mark.asyncio
214
+ async def test_custom_handler(self, base_acp_server):
215
+ handler_called = False
216
+
217
+ @base_acp_server.on_task_event_send
218
+ async def test_handler(params: SendEventParams):
219
+ nonlocal handler_called
220
+ handler_called = True
221
+ return {"handled": True}
222
+
223
+ # Test handler execution
224
+ params = SendEventParams(...)
225
+ result = await base_acp_server._handlers[RPCMethod.EVENT_SEND](params)
226
+
227
+ assert handler_called is True
228
+ assert result["handled"] is True
229
+ ```
230
+
231
+ ## Continuous Integration
232
+
233
+ The test suite is designed to work well in CI environments:
234
+
235
+ - Tests are isolated and don't interfere with each other
236
+ - Ports are dynamically allocated to avoid conflicts
237
+ - Background tasks are properly cleaned up
238
+ - Timeouts are reasonable for CI environments
239
+
240
+ ### CI Configuration Example
241
+
242
+ ```yaml
243
+ # .github/workflows/test.yml
244
+ name: Tests
245
+ on: [push, pull_request]
246
+ jobs:
247
+ test:
248
+ runs-on: ubuntu-latest
249
+ steps:
250
+ - uses: actions/checkout@v2
251
+ - uses: actions/setup-python@v2
252
+ with:
253
+ python-version: '3.9'
254
+ - run: pip install -r requirements.txt
255
+ - run: pip install pytest pytest-asyncio httpx pytest-cov
256
+ - run: cd agentex/sdk/fastacp/tests && python run_tests.py --coverage
257
+ ```
258
+
259
+ ## Troubleshooting
260
+
261
+ ### Common Issues
262
+
263
+ 1. **Port conflicts**: Tests use dynamic port allocation, but if you see port conflicts, try running tests sequentially:
264
+ ```bash
265
+ python run_tests.py --parallel 1
266
+ ```
267
+
268
+ 2. **Async test failures**: Make sure all async tests are marked with `@pytest.mark.asyncio`
269
+
270
+ 3. **Handler not found errors**: Ensure handlers are properly registered before testing
271
+
272
+ 4. **Timeout issues**: Some tests have built-in delays for background processing. If tests are flaky, increase sleep times in test code.
273
+
274
+ ### Debug Mode
275
+
276
+ Run tests with maximum verbosity and no capture:
277
+ ```bash
278
+ pytest -vvv -s --tb=long
279
+ ```
280
+
281
+ ### Memory Issues
282
+
283
+ If you encounter memory issues with large tests:
284
+ ```bash
285
+ python run_tests.py --markers "not memory_intensive"
286
+ ```
287
+
288
+ ## Contributing
289
+
290
+ When adding new tests:
291
+
292
+ 1. Follow the existing test structure and naming conventions
293
+ 2. Add appropriate docstrings explaining what the test does
294
+ 3. Use fixtures for common setup
295
+ 4. Clean up resources properly (especially in integration tests)
296
+ 5. Add tests to the appropriate category in `run_tests.py`
297
+ 6. Update this README if adding new test categories or significant functionality
@@ -0,0 +1,307 @@
1
+ import asyncio
2
+ import socket
3
+ import time
4
+ from typing import Any
5
+ from unittest.mock import AsyncMock, patch
6
+
7
+ import httpx
8
+ import pytest
9
+ import pytest_asyncio
10
+ import uvicorn
11
+
12
+ from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer
13
+ from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP
14
+ from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP
15
+ from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP
16
+ from agentex.lib.types.acp import (
17
+ CancelTaskParams,
18
+ CreateTaskParams,
19
+ SendMessageParams,
20
+ )
21
+ from agentex.lib.types.json_rpc import JSONRPCRequest
22
+ from agentex.types.agent import Agent
23
+ from agentex.types.task_message import TaskMessageContent
24
+ from agentex.types.task_message_content import TextContent
25
+ from agentex.types.task import Task
26
+
27
+ # Configure pytest-asyncio
28
+ pytest_plugins = ("pytest_asyncio",)
29
+
30
+
31
+ def find_free_port() -> int:
32
+ """Find a free port for testing"""
33
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
34
+ s.bind(("", 0))
35
+ s.listen(1)
36
+ port = s.getsockname()[1]
37
+ return port
38
+
39
+
40
+ @pytest.fixture
41
+ def free_port() -> int:
42
+ """Fixture that provides a free port for testing"""
43
+ return find_free_port()
44
+
45
+
46
+ @pytest.fixture
47
+ def sample_task() -> Task:
48
+ """Fixture that provides a sample Task object"""
49
+ return Task(
50
+ id="test-task-123", agent_id="test-agent-456", status=TaskStatus.RUNNING
51
+ )
52
+
53
+
54
+ @pytest.fixture
55
+ def sample_message_content() -> TaskMessageContent:
56
+ """Fixture that provides a sample TaskMessage object"""
57
+ return TextContent(
58
+ type="text",
59
+ author="user",
60
+ content="Hello, this is a test message",
61
+ )
62
+
63
+
64
+ @pytest.fixture
65
+ def sample_send_message_params(
66
+ sample_task: Task, sample_message_content: TaskMessageContent
67
+ ) -> SendMessageParams:
68
+ """Fixture that provides sample SendMessageParams"""
69
+ return SendMessageParams(
70
+ agent=Agent(
71
+ id="test-agent-456",
72
+ name="test-agent",
73
+ description="test-agent",
74
+ acp_type="sync",
75
+ ),
76
+ task=sample_task,
77
+ content=sample_message_content,
78
+ stream=False,
79
+ )
80
+
81
+
82
+ @pytest.fixture
83
+ def sample_cancel_task_params() -> CancelTaskParams:
84
+ """Fixture that provides sample CancelTaskParams"""
85
+ return CancelTaskParams(
86
+ agent=Agent(id="test-agent-456", name="test-agent", description="test-agent", acp_type="sync"),
87
+ task=Task(id="test-task-123", agent_id="test-agent-456", status="running"),
88
+ )
89
+
90
+
91
+ @pytest.fixture
92
+ def sample_create_task_params(sample_task: Task) -> CreateTaskParams:
93
+ """Fixture that provides sample CreateTaskParams"""
94
+ return CreateTaskParams(
95
+ agent=Agent(id="test-agent-456", name="test-agent", description="test-agent", acp_type="sync"),
96
+ task=sample_task,
97
+ params={},
98
+ )
99
+
100
+
101
+ class TestServerRunner:
102
+ """Utility class for running test servers"""
103
+
104
+ def __init__(self, app: BaseACPServer, port: int):
105
+ self.app = app
106
+ self.port = port
107
+ self.server = None
108
+ self.server_task = None
109
+
110
+ async def start(self):
111
+ """Start the server in a background task"""
112
+ config = uvicorn.Config(
113
+ app=self.app,
114
+ host="127.0.0.1",
115
+ port=self.port,
116
+ log_level="error", # Reduce noise in tests
117
+ )
118
+ self.server = uvicorn.Server(config)
119
+ self.server_task = asyncio.create_task(self.server.serve())
120
+
121
+ # Wait for server to be ready
122
+ await self._wait_for_server()
123
+
124
+ async def stop(self):
125
+ """Stop the server"""
126
+ if self.server:
127
+ self.server.should_exit = True
128
+ if self.server_task:
129
+ try:
130
+ await asyncio.wait_for(self.server_task, timeout=5.0)
131
+ except TimeoutError:
132
+ self.server_task.cancel()
133
+ try:
134
+ await self.server_task
135
+ except asyncio.CancelledError:
136
+ pass
137
+
138
+ async def _wait_for_server(self, timeout: float = 10.0):
139
+ """Wait for server to be ready to accept connections"""
140
+ start_time = time.time()
141
+ while time.time() - start_time < timeout:
142
+ try:
143
+ async with httpx.AsyncClient() as client:
144
+ response = await client.get(f"http://127.0.0.1:{self.port}/healthz")
145
+ if response.status_code == 200:
146
+ return
147
+ except (httpx.ConnectError, httpx.ConnectTimeout):
148
+ await asyncio.sleep(0.1)
149
+ raise TimeoutError(f"Server did not start within {timeout} seconds")
150
+
151
+
152
+ @pytest_asyncio.fixture
153
+ async def test_server_runner():
154
+ """Fixture that provides a TestServerRunner factory"""
155
+ runners = []
156
+
157
+ def create_runner(app: BaseACPServer, port: int) -> TestServerRunner:
158
+ runner = TestServerRunner(app, port)
159
+ runners.append(runner)
160
+ return runner
161
+
162
+ yield create_runner
163
+
164
+ # Cleanup all runners
165
+ for runner in runners:
166
+ await runner.stop()
167
+
168
+
169
+ @pytest.fixture
170
+ def base_acp_server():
171
+ """Fixture that provides a BaseACPServer instance for sync tests"""
172
+ with patch.dict(
173
+ "os.environ", {"AGENTEX_BASE_URL": ""}
174
+ ): # Disable agent registration
175
+ server = BaseACPServer()
176
+ return server
177
+
178
+
179
+ @pytest_asyncio.fixture
180
+ async def async_base_acp_server():
181
+ """Fixture that provides a BaseACPServer instance for async tests"""
182
+ with patch.dict(
183
+ "os.environ", {"AGENTEX_BASE_URL": ""}
184
+ ): # Disable agent registration
185
+ server = BaseACPServer.create()
186
+ return server
187
+
188
+
189
+ @pytest.fixture
190
+ def sync_acp_server():
191
+ """Fixture that provides a SyncACP instance for sync tests"""
192
+ with patch.dict(
193
+ "os.environ", {"AGENTEX_BASE_URL": ""}
194
+ ): # Disable agent registration
195
+ server = SyncACP()
196
+ return server
197
+
198
+
199
+ @pytest_asyncio.fixture
200
+ async def async_sync_acp_server():
201
+ """Fixture that provides a SyncACP instance for async tests"""
202
+ with patch.dict(
203
+ "os.environ", {"AGENTEX_BASE_URL": ""}
204
+ ): # Disable agent registration
205
+ server = await SyncACP.create()
206
+ return server
207
+
208
+
209
+ @pytest.fixture
210
+ def agentic_base_acp_server():
211
+ """Fixture that provides an AgenticBaseACP instance for sync tests"""
212
+ with patch.dict(
213
+ "os.environ", {"AGENTEX_BASE_URL": ""}
214
+ ): # Disable agent registration
215
+ server = AgenticBaseACP()
216
+ return server
217
+
218
+
219
+ @pytest_asyncio.fixture
220
+ async def async_agentic_base_acp_server():
221
+ """Fixture that provides an AgenticBaseACP instance for async tests"""
222
+ with patch.dict(
223
+ "os.environ", {"AGENTEX_BASE_URL": ""}
224
+ ): # Disable agent registration
225
+ server = await AgenticBaseACP.create()
226
+ return server
227
+
228
+
229
+ @pytest_asyncio.fixture
230
+ async def mock_temporal_acp_server():
231
+ """Fixture that provides a mocked TemporalACP instance"""
232
+ with patch.dict(
233
+ "os.environ", {"AGENTEX_BASE_URL": ""}
234
+ ): # Disable agent registration
235
+ with patch(
236
+ "agentex.sdk.fastacp.impl.temporal_acp.TemporalClient"
237
+ ) as mock_temporal_client:
238
+ with patch(
239
+ "agentex.sdk.fastacp.impl.temporal_acp.AsyncAgentexClient"
240
+ ) as mock_agentex_client:
241
+ # Mock the temporal client creation
242
+ mock_temporal_client.create.return_value = AsyncMock()
243
+ mock_agentex_client.return_value = AsyncMock()
244
+
245
+ server = await TemporalACP.create(temporal_address="localhost:7233")
246
+ return server
247
+
248
+
249
+ class JSONRPCTestClient:
250
+ """Test client for making JSON-RPC requests"""
251
+
252
+ def __init__(self, base_url: str):
253
+ self.base_url = base_url
254
+
255
+ async def call_method(
256
+ self, method: str, params: dict[str, Any], request_id: str | None = "test-1"
257
+ ) -> dict[str, Any]:
258
+ """Make a JSON-RPC method call"""
259
+ request = JSONRPCRequest(method=method, params=params, id=request_id)
260
+
261
+ async with httpx.AsyncClient() as client:
262
+ response = await client.post(
263
+ f"{self.base_url}/api",
264
+ json=request.model_dump(),
265
+ headers={"Content-Type": "application/json"},
266
+ )
267
+ return response.json()
268
+
269
+ async def send_notification(
270
+ self, method: str, params: dict[str, Any]
271
+ ) -> dict[str, Any]:
272
+ """Send a JSON-RPC notification (no ID)"""
273
+ return await self.call_method(method, params, request_id=None)
274
+
275
+ async def health_check(self) -> dict[str, Any]:
276
+ """Check server health"""
277
+ async with httpx.AsyncClient() as client:
278
+ response = await client.get(f"{self.base_url}/healthz")
279
+ return response.json()
280
+
281
+
282
+ @pytest.fixture
283
+ def jsonrpc_client_factory():
284
+ """Fixture that provides a JSONRPCTestClient factory"""
285
+
286
+ def create_client(base_url: str) -> JSONRPCTestClient:
287
+ return JSONRPCTestClient(base_url)
288
+
289
+ return create_client
290
+
291
+
292
+ # Mock environment variables for testing
293
+ @pytest.fixture
294
+ def mock_env_vars():
295
+ """Fixture that mocks environment variables"""
296
+ env_vars = {
297
+ "AGENTEX_BASE_URL": "", # Disable agent registration by default
298
+ "AGENT_NAME": "test-agent",
299
+ "AGENT_DESCRIPTION": "Test agent description",
300
+ "ACP_URL": "http://localhost",
301
+ "ACP_PORT": "8000",
302
+ "WORKFLOW_NAME": "test-workflow",
303
+ "WORKFLOW_TASK_QUEUE": "test-queue",
304
+ }
305
+
306
+ with patch.dict("os.environ", env_vars):
307
+ yield env_vars
@@ -0,0 +1,10 @@
1
+ [tool:pytest]
2
+ asyncio_mode = auto
3
+ addopts = -v --tb=short
4
+ testpaths = .
5
+ python_files = test_*.py
6
+ python_classes = Test*
7
+ python_functions = test_*
8
+ filterwarnings =
9
+ ignore::DeprecationWarning
10
+ ignore::PytestDeprecationWarning