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.
- agentex/__init__.py +103 -0
- agentex/_base_client.py +1992 -0
- agentex/_client.py +506 -0
- agentex/_compat.py +219 -0
- agentex/_constants.py +14 -0
- agentex/_exceptions.py +108 -0
- agentex/_files.py +123 -0
- agentex/_models.py +829 -0
- agentex/_qs.py +150 -0
- agentex/_resource.py +43 -0
- agentex/_response.py +830 -0
- agentex/_streaming.py +333 -0
- agentex/_types.py +219 -0
- agentex/_utils/__init__.py +57 -0
- agentex/_utils/_logs.py +25 -0
- agentex/_utils/_proxy.py +65 -0
- agentex/_utils/_reflection.py +42 -0
- agentex/_utils/_resources_proxy.py +24 -0
- agentex/_utils/_streams.py +12 -0
- agentex/_utils/_sync.py +86 -0
- agentex/_utils/_transform.py +447 -0
- agentex/_utils/_typing.py +151 -0
- agentex/_utils/_utils.py +422 -0
- agentex/_version.py +4 -0
- agentex/lib/.keep +4 -0
- agentex/lib/__init__.py +0 -0
- agentex/lib/adk/__init__.py +41 -0
- agentex/lib/adk/_modules/__init__.py +0 -0
- agentex/lib/adk/_modules/acp.py +247 -0
- agentex/lib/adk/_modules/agent_task_tracker.py +176 -0
- agentex/lib/adk/_modules/agents.py +77 -0
- agentex/lib/adk/_modules/events.py +141 -0
- agentex/lib/adk/_modules/messages.py +285 -0
- agentex/lib/adk/_modules/state.py +291 -0
- agentex/lib/adk/_modules/streaming.py +75 -0
- agentex/lib/adk/_modules/tasks.py +124 -0
- agentex/lib/adk/_modules/tracing.py +194 -0
- agentex/lib/adk/providers/__init__.py +9 -0
- agentex/lib/adk/providers/_modules/__init__.py +0 -0
- agentex/lib/adk/providers/_modules/litellm.py +232 -0
- agentex/lib/adk/providers/_modules/openai.py +416 -0
- agentex/lib/adk/providers/_modules/sgp.py +85 -0
- agentex/lib/adk/utils/__init__.py +5 -0
- agentex/lib/adk/utils/_modules/__init__.py +0 -0
- agentex/lib/adk/utils/_modules/templating.py +94 -0
- agentex/lib/cli/__init__.py +0 -0
- agentex/lib/cli/commands/__init__.py +0 -0
- agentex/lib/cli/commands/agents.py +328 -0
- agentex/lib/cli/commands/init.py +227 -0
- agentex/lib/cli/commands/main.py +33 -0
- agentex/lib/cli/commands/secrets.py +169 -0
- agentex/lib/cli/commands/tasks.py +118 -0
- agentex/lib/cli/commands/uv.py +133 -0
- agentex/lib/cli/handlers/__init__.py +0 -0
- agentex/lib/cli/handlers/agent_handlers.py +160 -0
- agentex/lib/cli/handlers/cleanup_handlers.py +186 -0
- agentex/lib/cli/handlers/deploy_handlers.py +351 -0
- agentex/lib/cli/handlers/run_handlers.py +452 -0
- agentex/lib/cli/handlers/secret_handlers.py +670 -0
- agentex/lib/cli/templates/default/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/default/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/default/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/default/README.md.j2 +193 -0
- agentex/lib/cli/templates/default/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/default/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/default/project/acp.py.j2 +29 -0
- agentex/lib/cli/templates/default/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/default/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/deploy/Screenshot 2025-03-19 at 10.36.57/342/200/257AM.png +0 -0
- agentex/lib/cli/templates/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/sync/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/sync/README.md.j2 +293 -0
- agentex/lib/cli/templates/sync/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/sync/project/acp.py.j2 +26 -0
- agentex/lib/cli/templates/sync/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/sync/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/temporal/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +48 -0
- agentex/lib/cli/templates/temporal/Dockerfile.j2 +48 -0
- agentex/lib/cli/templates/temporal/README.md.j2 +316 -0
- agentex/lib/cli/templates/temporal/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/temporal/manifest.yaml.j2 +137 -0
- agentex/lib/cli/templates/temporal/project/acp.py.j2 +30 -0
- agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +33 -0
- agentex/lib/cli/templates/temporal/project/workflow.py.j2 +66 -0
- agentex/lib/cli/templates/temporal/pyproject.toml.j2 +34 -0
- agentex/lib/cli/templates/temporal/requirements.txt.j2 +5 -0
- agentex/lib/cli/utils/cli_utils.py +14 -0
- agentex/lib/cli/utils/credential_utils.py +103 -0
- agentex/lib/cli/utils/exceptions.py +6 -0
- agentex/lib/cli/utils/kubectl_utils.py +135 -0
- agentex/lib/cli/utils/kubernetes_secrets_utils.py +185 -0
- agentex/lib/core/__init__.py +0 -0
- agentex/lib/core/adapters/__init__.py +0 -0
- agentex/lib/core/adapters/llm/__init__.py +1 -0
- agentex/lib/core/adapters/llm/adapter_litellm.py +46 -0
- agentex/lib/core/adapters/llm/adapter_sgp.py +55 -0
- agentex/lib/core/adapters/llm/port.py +24 -0
- agentex/lib/core/adapters/streams/adapter_redis.py +128 -0
- agentex/lib/core/adapters/streams/port.py +50 -0
- agentex/lib/core/clients/__init__.py +1 -0
- agentex/lib/core/clients/temporal/__init__.py +0 -0
- agentex/lib/core/clients/temporal/temporal_client.py +181 -0
- agentex/lib/core/clients/temporal/types.py +47 -0
- agentex/lib/core/clients/temporal/utils.py +56 -0
- agentex/lib/core/services/__init__.py +0 -0
- agentex/lib/core/services/adk/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/acp.py +210 -0
- agentex/lib/core/services/adk/agent_task_tracker.py +85 -0
- agentex/lib/core/services/adk/agents.py +43 -0
- agentex/lib/core/services/adk/events.py +61 -0
- agentex/lib/core/services/adk/messages.py +164 -0
- agentex/lib/core/services/adk/providers/__init__.py +0 -0
- agentex/lib/core/services/adk/providers/litellm.py +256 -0
- agentex/lib/core/services/adk/providers/openai.py +723 -0
- agentex/lib/core/services/adk/providers/sgp.py +99 -0
- agentex/lib/core/services/adk/state.py +120 -0
- agentex/lib/core/services/adk/streaming.py +262 -0
- agentex/lib/core/services/adk/tasks.py +69 -0
- agentex/lib/core/services/adk/tracing.py +36 -0
- agentex/lib/core/services/adk/utils/__init__.py +0 -0
- agentex/lib/core/services/adk/utils/templating.py +58 -0
- agentex/lib/core/temporal/__init__.py +0 -0
- agentex/lib/core/temporal/activities/__init__.py +207 -0
- agentex/lib/core/temporal/activities/activity_helpers.py +37 -0
- agentex/lib/core/temporal/activities/adk/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +86 -0
- agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +76 -0
- agentex/lib/core/temporal/activities/adk/agents_activities.py +35 -0
- agentex/lib/core/temporal/activities/adk/events_activities.py +50 -0
- agentex/lib/core/temporal/activities/adk/messages_activities.py +94 -0
- agentex/lib/core/temporal/activities/adk/providers/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +71 -0
- agentex/lib/core/temporal/activities/adk/providers/openai_activities.py +210 -0
- agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +42 -0
- agentex/lib/core/temporal/activities/adk/state_activities.py +85 -0
- agentex/lib/core/temporal/activities/adk/streaming_activities.py +33 -0
- agentex/lib/core/temporal/activities/adk/tasks_activities.py +48 -0
- agentex/lib/core/temporal/activities/adk/tracing_activities.py +55 -0
- agentex/lib/core/temporal/activities/adk/utils/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +41 -0
- agentex/lib/core/temporal/services/__init__.py +0 -0
- agentex/lib/core/temporal/services/temporal_task_service.py +69 -0
- agentex/lib/core/temporal/types/__init__.py +0 -0
- agentex/lib/core/temporal/types/workflow.py +5 -0
- agentex/lib/core/temporal/workers/__init__.py +0 -0
- agentex/lib/core/temporal/workers/worker.py +162 -0
- agentex/lib/core/temporal/workflows/workflow.py +26 -0
- agentex/lib/core/tracing/__init__.py +5 -0
- agentex/lib/core/tracing/processors/agentex_tracing_processor.py +117 -0
- agentex/lib/core/tracing/processors/sgp_tracing_processor.py +119 -0
- agentex/lib/core/tracing/processors/tracing_processor_interface.py +40 -0
- agentex/lib/core/tracing/trace.py +311 -0
- agentex/lib/core/tracing/tracer.py +70 -0
- agentex/lib/core/tracing/tracing_processor_manager.py +62 -0
- agentex/lib/environment_variables.py +87 -0
- agentex/lib/py.typed +0 -0
- agentex/lib/sdk/__init__.py +0 -0
- agentex/lib/sdk/config/__init__.py +0 -0
- agentex/lib/sdk/config/agent_config.py +61 -0
- agentex/lib/sdk/config/agent_manifest.py +219 -0
- agentex/lib/sdk/config/build_config.py +35 -0
- agentex/lib/sdk/config/deployment_config.py +117 -0
- agentex/lib/sdk/config/local_development_config.py +56 -0
- agentex/lib/sdk/config/project_config.py +103 -0
- agentex/lib/sdk/fastacp/__init__.py +3 -0
- agentex/lib/sdk/fastacp/base/base_acp_server.py +406 -0
- agentex/lib/sdk/fastacp/fastacp.py +74 -0
- agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +72 -0
- agentex/lib/sdk/fastacp/impl/sync_acp.py +109 -0
- agentex/lib/sdk/fastacp/impl/temporal_acp.py +97 -0
- agentex/lib/sdk/fastacp/tests/README.md +297 -0
- agentex/lib/sdk/fastacp/tests/conftest.py +307 -0
- agentex/lib/sdk/fastacp/tests/pytest.ini +10 -0
- agentex/lib/sdk/fastacp/tests/run_tests.py +227 -0
- agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +450 -0
- agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +344 -0
- agentex/lib/sdk/fastacp/tests/test_integration.py +477 -0
- agentex/lib/sdk/state_machine/__init__.py +6 -0
- agentex/lib/sdk/state_machine/noop_workflow.py +21 -0
- agentex/lib/sdk/state_machine/state.py +10 -0
- agentex/lib/sdk/state_machine/state_machine.py +189 -0
- agentex/lib/sdk/state_machine/state_workflow.py +16 -0
- agentex/lib/sdk/utils/__init__.py +0 -0
- agentex/lib/sdk/utils/messages.py +223 -0
- agentex/lib/types/__init__.py +0 -0
- agentex/lib/types/acp.py +94 -0
- agentex/lib/types/agent_configs.py +79 -0
- agentex/lib/types/agent_results.py +29 -0
- agentex/lib/types/credentials.py +34 -0
- agentex/lib/types/fastacp.py +61 -0
- agentex/lib/types/files.py +13 -0
- agentex/lib/types/json_rpc.py +49 -0
- agentex/lib/types/llm_messages.py +354 -0
- agentex/lib/types/task_message_updates.py +171 -0
- agentex/lib/types/tracing.py +34 -0
- agentex/lib/utils/__init__.py +0 -0
- agentex/lib/utils/completions.py +131 -0
- agentex/lib/utils/console.py +14 -0
- agentex/lib/utils/io.py +29 -0
- agentex/lib/utils/iterables.py +14 -0
- agentex/lib/utils/json_schema.py +23 -0
- agentex/lib/utils/logging.py +31 -0
- agentex/lib/utils/mcp.py +17 -0
- agentex/lib/utils/model_utils.py +46 -0
- agentex/lib/utils/parsing.py +15 -0
- agentex/lib/utils/regex.py +6 -0
- agentex/lib/utils/temporal.py +13 -0
- agentex/py.typed +0 -0
- agentex/resources/__init__.py +103 -0
- agentex/resources/agents.py +707 -0
- agentex/resources/events.py +294 -0
- agentex/resources/messages/__init__.py +33 -0
- agentex/resources/messages/batch.py +271 -0
- agentex/resources/messages/messages.py +492 -0
- agentex/resources/spans.py +557 -0
- agentex/resources/states.py +544 -0
- agentex/resources/tasks.py +615 -0
- agentex/resources/tracker.py +384 -0
- agentex/types/__init__.py +56 -0
- agentex/types/acp_type.py +7 -0
- agentex/types/agent.py +29 -0
- agentex/types/agent_list_params.py +13 -0
- agentex/types/agent_list_response.py +10 -0
- agentex/types/agent_rpc_by_name_params.py +21 -0
- agentex/types/agent_rpc_params.py +51 -0
- agentex/types/agent_rpc_params1.py +21 -0
- agentex/types/agent_rpc_response.py +20 -0
- agentex/types/agent_rpc_result.py +90 -0
- agentex/types/agent_task_tracker.py +34 -0
- agentex/types/data_content.py +30 -0
- agentex/types/data_content_param.py +31 -0
- agentex/types/data_delta.py +14 -0
- agentex/types/event.py +29 -0
- agentex/types/event_list_params.py +22 -0
- agentex/types/event_list_response.py +10 -0
- agentex/types/message_author.py +7 -0
- agentex/types/message_create_params.py +18 -0
- agentex/types/message_list_params.py +14 -0
- agentex/types/message_list_response.py +10 -0
- agentex/types/message_style.py +7 -0
- agentex/types/message_update_params.py +18 -0
- agentex/types/messages/__init__.py +8 -0
- agentex/types/messages/batch_create_params.py +16 -0
- agentex/types/messages/batch_create_response.py +10 -0
- agentex/types/messages/batch_update_params.py +16 -0
- agentex/types/messages/batch_update_response.py +10 -0
- agentex/types/shared/__init__.py +3 -0
- agentex/types/shared/task_message_update.py +83 -0
- agentex/types/span.py +36 -0
- agentex/types/span_create_params.py +40 -0
- agentex/types/span_list_params.py +12 -0
- agentex/types/span_list_response.py +10 -0
- agentex/types/span_update_params.py +37 -0
- agentex/types/state.py +25 -0
- agentex/types/state_create_params.py +16 -0
- agentex/types/state_list_params.py +16 -0
- agentex/types/state_list_response.py +10 -0
- agentex/types/state_update_params.py +16 -0
- agentex/types/task.py +23 -0
- agentex/types/task_delete_by_name_response.py +8 -0
- agentex/types/task_delete_response.py +8 -0
- agentex/types/task_list_response.py +10 -0
- agentex/types/task_message.py +33 -0
- agentex/types/task_message_content.py +16 -0
- agentex/types/task_message_content_param.py +17 -0
- agentex/types/task_message_delta.py +16 -0
- agentex/types/text_content.py +53 -0
- agentex/types/text_content_param.py +54 -0
- agentex/types/text_delta.py +14 -0
- agentex/types/tool_request_content.py +36 -0
- agentex/types/tool_request_content_param.py +37 -0
- agentex/types/tool_request_delta.py +18 -0
- agentex/types/tool_response_content.py +36 -0
- agentex/types/tool_response_content_param.py +36 -0
- agentex/types/tool_response_delta.py +18 -0
- agentex/types/tracker_list_params.py +16 -0
- agentex/types/tracker_list_response.py +10 -0
- agentex/types/tracker_update_params.py +19 -0
- agentex_sdk-0.1.0a6.dist-info/METADATA +426 -0
- agentex_sdk-0.1.0a6.dist-info/RECORD +289 -0
- agentex_sdk-0.1.0a6.dist-info/WHEEL +4 -0
- agentex_sdk-0.1.0a6.dist-info/entry_points.txt +2 -0
- 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
|