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,227 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test runner for BaseACPServer and implementations.
4
+
5
+ This script provides various options for running the test suite:
6
+ - Run all tests
7
+ - Run specific test categories
8
+ - Run with different verbosity levels
9
+ - Generate coverage reports
10
+ - Run performance tests
11
+ """
12
+
13
+ import argparse
14
+ import subprocess
15
+ import sys
16
+ from pathlib import Path
17
+
18
+
19
+ def run_command(cmd, description=""):
20
+ """Run a command and return the result"""
21
+ if description:
22
+ print(f"\n{'='*60}")
23
+ print(f"Running: {description}")
24
+ print(f"Command: {' '.join(cmd)}")
25
+ print(f"{'='*60}")
26
+
27
+ result = subprocess.run(cmd, capture_output=True, text=True, check=False)
28
+
29
+ if result.stdout:
30
+ print(result.stdout)
31
+ if result.stderr:
32
+ print(result.stderr, file=sys.stderr)
33
+
34
+ return result.returncode == 0
35
+
36
+
37
+ def main():
38
+ parser = argparse.ArgumentParser(description="Run BaseACPServer tests")
39
+ parser.add_argument(
40
+ "--category",
41
+ choices=["unit", "integration", "implementations", "error", "all"],
42
+ default="all",
43
+ help="Test category to run",
44
+ )
45
+ parser.add_argument(
46
+ "--verbose",
47
+ "-v",
48
+ action="count",
49
+ default=0,
50
+ help="Increase verbosity (use -v, -vv, or -vvv)",
51
+ )
52
+ parser.add_argument("--coverage", action="store_true", help="Run with coverage reporting")
53
+ parser.add_argument(
54
+ "--parallel", "-n", type=int, help="Run tests in parallel (number of workers)"
55
+ )
56
+ parser.add_argument(
57
+ "--markers", "-m", help="Run tests with specific markers (e.g., 'not slow')"
58
+ )
59
+ parser.add_argument("--failfast", "-x", action="store_true", help="Stop on first failure")
60
+ parser.add_argument(
61
+ "--lf",
62
+ "--last-failed",
63
+ action="store_true",
64
+ help="Run only tests that failed in the last run",
65
+ )
66
+ parser.add_argument(
67
+ "--collect-only", action="store_true", help="Only collect tests, don't run them"
68
+ )
69
+
70
+ args = parser.parse_args()
71
+
72
+ # Base pytest command
73
+ cmd = ["python", "-m", "pytest"]
74
+
75
+ # Add test files based on category
76
+ test_files = {
77
+ "unit": ["test_base_acp_server.py", "test_json_rpc_endpoints.py"],
78
+ "integration": ["test_server_integration.py"],
79
+ "implementations": ["test_implementations.py"],
80
+ "error": ["test_error_handling.py"],
81
+ "all": [
82
+ "test_base_acp_server.py",
83
+ "test_json_rpc_endpoints.py",
84
+ "test_server_integration.py",
85
+ "test_implementations.py",
86
+ "test_error_handling.py",
87
+ ],
88
+ }
89
+
90
+ # Add test files to command
91
+ for test_file in test_files[args.category]:
92
+ cmd.append(test_file)
93
+
94
+ # Add verbosity
95
+ if args.verbose:
96
+ cmd.append("-" + "v" * min(args.verbose, 3))
97
+
98
+ # Add coverage
99
+ if args.coverage:
100
+ cmd.extend(
101
+ [
102
+ "--cov=agentex.sdk.fastacp",
103
+ "--cov-report=html",
104
+ "--cov-report=term-missing",
105
+ "--cov-branch",
106
+ ]
107
+ )
108
+
109
+ # Add parallel execution
110
+ if args.parallel:
111
+ cmd.extend(["-n", str(args.parallel)])
112
+
113
+ # Add markers
114
+ if args.markers:
115
+ cmd.extend(["-m", args.markers])
116
+
117
+ # Add fail fast
118
+ if args.failfast:
119
+ cmd.append("-x")
120
+
121
+ # Add last failed
122
+ if args.lf:
123
+ cmd.append("--lf")
124
+
125
+ # Add collect only
126
+ if args.collect_only:
127
+ cmd.append("--collect-only")
128
+
129
+ # Add other useful options
130
+ cmd.extend(
131
+ [
132
+ "--tb=short", # Shorter traceback format
133
+ "--strict-markers", # Strict marker checking
134
+ "--disable-warnings", # Disable warnings for cleaner output
135
+ ]
136
+ )
137
+
138
+ # Change to test directory
139
+ test_dir = Path(__file__).parent
140
+ original_cwd = Path.cwd()
141
+
142
+ try:
143
+ import os
144
+
145
+ os.chdir(test_dir)
146
+
147
+ # Run the tests
148
+ success = run_command(cmd, f"Running {args.category} tests")
149
+
150
+ if success:
151
+ print(f"\n✅ All {args.category} tests passed!")
152
+ if args.coverage:
153
+ print("📊 Coverage report generated in htmlcov/")
154
+ else:
155
+ print(f"\n❌ Some {args.category} tests failed!")
156
+ return 1
157
+
158
+ finally:
159
+ os.chdir(original_cwd)
160
+
161
+ return 0
162
+
163
+
164
+ def run_quick_tests():
165
+ """Run a quick subset of tests for development"""
166
+ cmd = [
167
+ "python",
168
+ "-m",
169
+ "pytest",
170
+ "test_base_acp_server.py::TestBaseACPServerInitialization",
171
+ "test_json_rpc_endpoints.py::TestJSONRPCMethodHandling",
172
+ "-v",
173
+ "--tb=short",
174
+ ]
175
+
176
+ return run_command(cmd, "Running quick development tests")
177
+
178
+
179
+ def run_smoke_tests():
180
+ """Run smoke tests to verify basic functionality"""
181
+ cmd = [
182
+ "python",
183
+ "-m",
184
+ "pytest",
185
+ "-m",
186
+ "not slow",
187
+ "-x", # Stop on first failure
188
+ "--tb=line",
189
+ "test_base_acp_server.py::TestBaseACPServerInitialization::test_base_acp_server_init",
190
+ "test_base_acp_server.py::TestHealthCheckEndpoint::test_health_check_endpoint",
191
+ "test_json_rpc_endpoints.py::TestJSONRPCMethodHandling::test_message_received_method_routing",
192
+ ]
193
+
194
+ return run_command(cmd, "Running smoke tests")
195
+
196
+
197
+ def run_performance_tests():
198
+ """Run performance-focused tests"""
199
+ cmd = [
200
+ "python",
201
+ "-m",
202
+ "pytest",
203
+ "test_server_integration.py::TestServerPerformance",
204
+ "test_error_handling.py::TestServerErrorHandling::test_server_handles_concurrent_errors",
205
+ "-v",
206
+ "--tb=short",
207
+ ]
208
+
209
+ return run_command(cmd, "Running performance tests")
210
+
211
+
212
+ if __name__ == "__main__":
213
+ # Check if specific test type is requested via environment
214
+ test_type = (
215
+ sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] in ["quick", "smoke", "perf"] else None
216
+ )
217
+
218
+ if test_type == "quick":
219
+ success = run_quick_tests()
220
+ elif test_type == "smoke":
221
+ success = run_smoke_tests()
222
+ elif test_type == "perf":
223
+ success = run_performance_tests()
224
+ else:
225
+ success = main()
226
+
227
+ sys.exit(0 if success else 1)
@@ -0,0 +1,450 @@
1
+ import asyncio
2
+ from unittest.mock import patch
3
+
4
+ import pytest
5
+ from fastapi.testclient import TestClient
6
+
7
+ from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer
8
+ from agentex.lib.types.acp import (
9
+ CancelTaskParams,
10
+ RPCMethod,
11
+ SendEventParams,
12
+ )
13
+
14
+
15
+ class TestBaseACPServerInitialization:
16
+ """Test BaseACPServer initialization and setup"""
17
+
18
+ def test_base_acp_server_init(self):
19
+ """Test BaseACPServer initialization sets up routes correctly"""
20
+ with patch.dict("os.environ", {"AGENTEX_BASE_URL": ""}):
21
+ server = BaseACPServer()
22
+
23
+ # Check that FastAPI routes are set up
24
+ routes = [route.path for route in server.routes]
25
+ assert "/healthz" in routes
26
+ assert "/api" in routes
27
+
28
+ # Check that handlers dict is initialized
29
+ assert hasattr(server, "_handlers")
30
+ assert isinstance(server._handlers, dict)
31
+
32
+ def test_base_acp_server_create_classmethod(self):
33
+ """Test BaseACPServer.create() class method"""
34
+ with patch.dict("os.environ", {"AGENTEX_BASE_URL": ""}):
35
+ server = BaseACPServer.create()
36
+
37
+ assert isinstance(server, BaseACPServer)
38
+ assert hasattr(server, "_handlers")
39
+
40
+ def test_lifespan_function_setup(self):
41
+ """Test that lifespan function is properly configured"""
42
+ with patch.dict("os.environ", {"AGENTEX_BASE_URL": ""}):
43
+ server = BaseACPServer()
44
+
45
+ # Check that lifespan is configured
46
+ assert server.router.lifespan_context is not None
47
+
48
+
49
+ class TestHealthCheckEndpoint:
50
+ """Test health check endpoint functionality"""
51
+
52
+ def test_health_check_endpoint(self, base_acp_server):
53
+ """Test GET /healthz endpoint returns correct response"""
54
+ client = TestClient(base_acp_server)
55
+
56
+ response = client.get("/healthz")
57
+
58
+ assert response.status_code == 200
59
+ assert response.json() == {"status": "healthy"}
60
+
61
+ def test_health_check_content_type(self, base_acp_server):
62
+ """Test health check returns JSON content type"""
63
+ client = TestClient(base_acp_server)
64
+
65
+ response = client.get("/healthz")
66
+
67
+ assert response.headers["content-type"] == "application/json"
68
+
69
+
70
+ class TestJSONRPCEndpointCore:
71
+ """Test core JSON-RPC endpoint functionality"""
72
+
73
+ def test_jsonrpc_endpoint_exists(self, base_acp_server):
74
+ """Test POST /api endpoint exists"""
75
+ client = TestClient(base_acp_server)
76
+
77
+ # Send a basic request to check endpoint exists
78
+ response = client.post("/api", json={})
79
+
80
+ # Should not return 404 (endpoint exists)
81
+ assert response.status_code != 404
82
+
83
+ def test_jsonrpc_malformed_request(self, base_acp_server):
84
+ """Test JSON-RPC endpoint handles malformed requests"""
85
+ client = TestClient(base_acp_server)
86
+
87
+ # Send malformed JSON
88
+ response = client.post("/api", json={"invalid": "request"})
89
+
90
+ assert response.status_code == 200
91
+ data = response.json()
92
+ assert "error" in data
93
+ assert data["jsonrpc"] == "2.0"
94
+
95
+ def test_jsonrpc_method_not_found(self, base_acp_server):
96
+ """Test JSON-RPC method not found error"""
97
+ client = TestClient(base_acp_server)
98
+
99
+ request = {
100
+ "jsonrpc": "2.0",
101
+ "method": "nonexistent/method",
102
+ "params": {},
103
+ "id": "test-1",
104
+ }
105
+
106
+ response = client.post("/api", json=request)
107
+
108
+ assert response.status_code == 200
109
+ data = response.json()
110
+ assert "error" in data
111
+ assert data["error"]["code"] == -32601 # Method not found
112
+ assert data["id"] == "test-1"
113
+
114
+ def test_jsonrpc_valid_request_structure(self, base_acp_server):
115
+ """Test JSON-RPC request parsing with valid structure"""
116
+ client = TestClient(base_acp_server)
117
+
118
+ # Add a mock handler for testing
119
+ async def mock_handler(params):
120
+ return {"status": "success"}
121
+
122
+ base_acp_server._handlers[RPCMethod.EVENT_SEND] = mock_handler
123
+
124
+ request = {
125
+ "jsonrpc": "2.0",
126
+ "method": "event/send",
127
+ "params": {
128
+ "task": {"id": "test-task", "agent_id": "test-agent", "status": "RUNNING"},
129
+ "message": {
130
+ "type": "text",
131
+ "author": "user",
132
+ "content": "test message",
133
+ },
134
+ },
135
+ "id": "test-1",
136
+ }
137
+
138
+ response = client.post("/api", json=request)
139
+
140
+ assert response.status_code == 200
141
+ data = response.json()
142
+ assert data["jsonrpc"] == "2.0"
143
+ assert data["id"] == "test-1"
144
+ print("DATA", data)
145
+ # Should return immediate acknowledgment
146
+ assert data["result"]["status"] == "processing"
147
+
148
+
149
+ class TestHandlerRegistration:
150
+ """Test handler registration and management"""
151
+
152
+ def test_on_task_event_send_decorator(self):
153
+ """Test on_task_event_send decorator registration"""
154
+ with patch.dict("os.environ", {"AGENTEX_BASE_URL": ""}):
155
+ server = BaseACPServer()
156
+
157
+ @server.on_task_event_send
158
+ async def test_handler(params: SendEventParams):
159
+ return {"test": "response"}
160
+
161
+ # Check handler is registered
162
+ assert RPCMethod.EVENT_SEND in server._handlers
163
+ assert server._handlers[RPCMethod.EVENT_SEND] is not None
164
+
165
+ def test_cancel_task_decorator(self):
166
+ """Test cancel_task decorator registration"""
167
+ with patch.dict("os.environ", {"AGENTEX_BASE_URL": ""}):
168
+ server = BaseACPServer()
169
+
170
+ @server.on_task_cancel
171
+ async def test_handler(params: CancelTaskParams):
172
+ return {"test": "response"}
173
+
174
+ # Check handler is registered
175
+ assert RPCMethod.TASK_CANCEL in server._handlers
176
+ assert server._handlers[RPCMethod.TASK_CANCEL] is not None
177
+
178
+ @pytest.mark.asyncio
179
+ async def test_handler_wrapper_functionality(self):
180
+ """Test that handler wrapper works correctly"""
181
+ with patch.dict("os.environ", {"AGENTEX_BASE_URL": ""}):
182
+ server = BaseACPServer()
183
+
184
+ # Create a test handler
185
+ async def test_handler(params):
186
+ return {"handler_called": True, "params_received": True}
187
+
188
+ # Wrap the handler
189
+ wrapped = server._wrap_handler(test_handler)
190
+
191
+ # Test the wrapped handler
192
+ result = await wrapped({"test": "params"})
193
+ assert result["handler_called"] is True
194
+ assert result["params_received"] is True
195
+
196
+
197
+ class TestBackgroundProcessing:
198
+ """Test background processing functionality"""
199
+
200
+ @pytest.mark.asyncio
201
+ async def test_notification_processing(self, async_base_acp_server):
202
+ """Test notification processing (requests with no ID)"""
203
+ # Add a mock handler
204
+ handler_called = False
205
+ received_params = None
206
+
207
+ async def mock_handler(params):
208
+ nonlocal handler_called, received_params
209
+ handler_called = True
210
+ received_params = params
211
+ return {"status": "processed"}
212
+
213
+ async_base_acp_server._handlers[RPCMethod.EVENT_SEND] = mock_handler
214
+
215
+ client = TestClient(async_base_acp_server)
216
+
217
+ request = {
218
+ "jsonrpc": "2.0",
219
+ "method": "event/send",
220
+ "params": {
221
+ "task": {"id": "test-task", "agent_id": "test-agent", "status": "RUNNING"},
222
+ "message": {
223
+ "type": "text",
224
+ "author": "user",
225
+ "content": "test message",
226
+ },
227
+ },
228
+ # No ID = notification
229
+ }
230
+
231
+ response = client.post("/api", json=request)
232
+
233
+ assert response.status_code == 200
234
+ data = response.json()
235
+ assert data["id"] is None # Notification response
236
+
237
+ # Give background task time to execute
238
+ await asyncio.sleep(0.1)
239
+
240
+ # Handler should have been called
241
+ assert handler_called is True
242
+ assert received_params is not None
243
+
244
+ @pytest.mark.asyncio
245
+ async def test_request_processing_with_id(self, async_base_acp_server):
246
+ """Test request processing with ID returns immediate acknowledgment"""
247
+
248
+ # Add a mock handler
249
+ async def mock_handler(params):
250
+ return {"status": "processed"}
251
+
252
+ async_base_acp_server._handlers[RPCMethod.TASK_CANCEL] = mock_handler
253
+
254
+ client = TestClient(async_base_acp_server)
255
+
256
+ request = {
257
+ "jsonrpc": "2.0",
258
+ "method": "task/cancel",
259
+ "params": {"task_id": "test-task-123"},
260
+ "id": "test-request-1",
261
+ }
262
+
263
+ response = client.post("/api", json=request)
264
+
265
+ assert response.status_code == 200
266
+ data = response.json()
267
+ assert data["jsonrpc"] == "2.0"
268
+ assert data["id"] == "test-request-1"
269
+ assert data["result"]["status"] == "processing" # Immediate acknowledgment
270
+
271
+
272
+ class TestSynchronousRPCMethods:
273
+ """Test synchronous RPC methods that return results immediately"""
274
+
275
+ def test_send_message_synchronous_response(self, base_acp_server):
276
+ """Test that MESSAGE_SEND method returns handler result synchronously"""
277
+ client = TestClient(base_acp_server)
278
+
279
+ # Add a mock handler that returns a specific result
280
+ async def mock_execute_handler(params):
281
+ return {
282
+ "task_id": params.task.id,
283
+ "message_content": params.message.content,
284
+ "status": "executed_synchronously",
285
+ "custom_data": {"processed": True, "timestamp": "2024-01-01T12:00:00Z"},
286
+ }
287
+
288
+ base_acp_server._handlers[RPCMethod.MESSAGE_SEND] = mock_execute_handler
289
+
290
+ request = {
291
+ "jsonrpc": "2.0",
292
+ "method": "message/send",
293
+ "params": {
294
+ "task": {"id": "test-task-123", "agent_id": "test-agent", "status": "RUNNING"},
295
+ "message": {
296
+ "type": "text",
297
+ "author": "user",
298
+ "content": "Execute this task please",
299
+ },
300
+ },
301
+ "id": "test-execute-1",
302
+ }
303
+
304
+ response = client.post("/api", json=request)
305
+
306
+ assert response.status_code == 200
307
+ data = response.json()
308
+
309
+ # Verify JSON-RPC structure
310
+ assert data["jsonrpc"] == "2.0"
311
+ assert data["id"] == "test-execute-1"
312
+ assert "result" in data
313
+ assert data.get("error") is None
314
+
315
+ # Verify the handler's result is returned directly (not "processing" status)
316
+ result = data["result"]
317
+ assert result["task_id"] == "test-task-123"
318
+ assert result["message_content"] == "Execute this task please"
319
+ assert result["status"] == "executed_synchronously"
320
+ assert result["custom_data"]["processed"] is True
321
+ assert result["custom_data"]["timestamp"] == "2024-01-01T12:00:00Z"
322
+
323
+ # Verify it's NOT the async "processing" response
324
+ assert result.get("status") != "processing"
325
+
326
+ def test_create_task_async_response(self, base_acp_server):
327
+ """Test that TASK_CREATE method returns processing status (async behavior)"""
328
+ client = TestClient(base_acp_server)
329
+
330
+ # Add a mock handler for init task
331
+ async def mock_init_handler(params):
332
+ return {
333
+ "task_id": params.task.id,
334
+ "status": "initialized",
335
+ }
336
+
337
+ base_acp_server._handlers[RPCMethod.TASK_CREATE] = mock_init_handler
338
+
339
+ request = {
340
+ "jsonrpc": "2.0",
341
+ "method": "task/create",
342
+ "params": {
343
+ "task": {"id": "test-task-456", "agent_id": "test-agent", "status": "RUNNING"}
344
+ },
345
+ "id": "test-init-1",
346
+ }
347
+
348
+ response = client.post("/api", json=request)
349
+
350
+ assert response.status_code == 200
351
+ data = response.json()
352
+
353
+ # Verify JSON-RPC structure
354
+ assert data["jsonrpc"] == "2.0"
355
+ assert data["id"] == "test-init-1"
356
+ assert "result" in data
357
+ assert data.get("error") is None
358
+
359
+ # Verify it returns async "processing" status (not the handler's result)
360
+ result = data["result"]
361
+ assert result["status"] == "processing"
362
+
363
+ # Verify it's NOT the handler's actual result
364
+ assert result.get("status") != "initialized"
365
+
366
+
367
+ class TestErrorHandling:
368
+ """Test error handling scenarios"""
369
+
370
+ def test_invalid_json_request(self, base_acp_server):
371
+ """Test handling of invalid JSON in request body"""
372
+ client = TestClient(base_acp_server)
373
+
374
+ # Send invalid JSON
375
+ response = client.post(
376
+ "/api", content="invalid json", headers={"Content-Type": "application/json"}
377
+ )
378
+
379
+ assert response.status_code == 200
380
+ data = response.json()
381
+ assert "error" in data
382
+ assert data["jsonrpc"] == "2.0"
383
+
384
+ def test_missing_required_fields(self, base_acp_server):
385
+ """Test handling of requests missing required JSON-RPC fields"""
386
+ client = TestClient(base_acp_server)
387
+
388
+ # Missing method field
389
+ request = {"jsonrpc": "2.0", "params": {}, "id": "test-1"}
390
+
391
+ response = client.post("/api", json=request)
392
+
393
+ assert response.status_code == 200
394
+ data = response.json()
395
+ assert "error" in data
396
+
397
+ def test_invalid_method_enum(self, base_acp_server):
398
+ """Test handling of invalid method names"""
399
+ client = TestClient(base_acp_server)
400
+
401
+ request = {
402
+ "jsonrpc": "2.0",
403
+ "method": "invalid/method/name",
404
+ "params": {},
405
+ "id": "test-1",
406
+ }
407
+
408
+ response = client.post("/api", json=request)
409
+
410
+ assert response.status_code == 200
411
+ data = response.json()
412
+ assert "error" in data
413
+ assert data["error"]["code"] == -32601 # Method not found
414
+
415
+ @pytest.mark.asyncio
416
+ async def test_handler_exception_handling(self, async_base_acp_server):
417
+ """Test that handler exceptions are properly handled"""
418
+
419
+ # Add a handler that raises an exception
420
+ async def failing_handler(params):
421
+ raise ValueError("Test exception")
422
+
423
+ async_base_acp_server._handlers[RPCMethod.EVENT_SEND] = failing_handler
424
+
425
+ client = TestClient(async_base_acp_server)
426
+
427
+ request = {
428
+ "jsonrpc": "2.0",
429
+ "method": "event/send",
430
+ "params": {
431
+ "task": {"id": "test-task", "agent_id": "test-agent", "status": "RUNNING"},
432
+ "message": {
433
+ "type": "text",
434
+ "author": "user",
435
+ "content": "test message",
436
+ },
437
+ },
438
+ "id": "test-1",
439
+ }
440
+
441
+ response = client.post("/api", json=request)
442
+
443
+ # Should still return immediate acknowledgment
444
+ assert response.status_code == 200
445
+ data = response.json()
446
+ assert data["result"]["status"] == "processing"
447
+
448
+ # Give background task time to fail
449
+ await asyncio.sleep(0.1)
450
+ # Exception should be logged but not crash the server