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,406 @@
|
|
1
|
+
import asyncio
|
2
|
+
import inspect
|
3
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
4
|
+
from contextlib import asynccontextmanager
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
import httpx
|
8
|
+
import uvicorn
|
9
|
+
from fastapi import FastAPI, Request
|
10
|
+
from fastapi.responses import StreamingResponse
|
11
|
+
from pydantic import TypeAdapter, ValidationError
|
12
|
+
|
13
|
+
# from agentex.lib.sdk.fastacp.types import BaseACPConfig
|
14
|
+
from agentex.lib.environment_variables import EnvironmentVariables
|
15
|
+
from agentex.lib.types.acp import (
|
16
|
+
PARAMS_MODEL_BY_METHOD,
|
17
|
+
RPC_SYNC_METHODS,
|
18
|
+
CancelTaskParams,
|
19
|
+
CreateTaskParams,
|
20
|
+
RPCMethod,
|
21
|
+
SendEventParams,
|
22
|
+
SendMessageParams,
|
23
|
+
)
|
24
|
+
from agentex.lib.types.json_rpc import JSONRPCError, JSONRPCRequest, JSONRPCResponse
|
25
|
+
from agentex.lib.types.task_message_updates import StreamTaskMessageFull, TaskMessageUpdate
|
26
|
+
from agentex.types.task_message_content import TaskMessageContent
|
27
|
+
from agentex.lib.utils.logging import make_logger
|
28
|
+
from agentex.lib.utils.model_utils import BaseModel
|
29
|
+
|
30
|
+
logger = make_logger(__name__)
|
31
|
+
|
32
|
+
# Create a TypeAdapter for TaskMessageUpdate validation
|
33
|
+
task_message_update_adapter = TypeAdapter(TaskMessageUpdate)
|
34
|
+
|
35
|
+
|
36
|
+
class BaseACPServer(FastAPI):
|
37
|
+
"""
|
38
|
+
AsyncAgentACP provides RPC-style hooks for agent events and commands asynchronously.
|
39
|
+
All methods follow JSON-RPC 2.0 format.
|
40
|
+
|
41
|
+
Available methods:
|
42
|
+
- event/send → Send a message to a task
|
43
|
+
- task/cancel → Cancel a task
|
44
|
+
- task/approve → Approve a task
|
45
|
+
"""
|
46
|
+
|
47
|
+
def __init__(self):
|
48
|
+
super().__init__(lifespan=self.get_lifespan_function())
|
49
|
+
|
50
|
+
self.get("/healthz")(self._healthz)
|
51
|
+
self.post("/api")(self._handle_jsonrpc)
|
52
|
+
|
53
|
+
# Method handlers
|
54
|
+
self._handlers: dict[RPCMethod, Callable] = {}
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def create(cls):
|
58
|
+
"""Create and initialize BaseACPServer instance"""
|
59
|
+
instance = cls()
|
60
|
+
instance._setup_handlers()
|
61
|
+
return instance
|
62
|
+
|
63
|
+
def _setup_handlers(self):
|
64
|
+
"""Set up default handlers - override in subclasses"""
|
65
|
+
# Base class has no default handlers
|
66
|
+
pass
|
67
|
+
|
68
|
+
def get_lifespan_function(self):
|
69
|
+
@asynccontextmanager
|
70
|
+
async def lifespan_context(app: FastAPI):
|
71
|
+
env_vars = EnvironmentVariables.refresh()
|
72
|
+
if env_vars.AGENTEX_BASE_URL:
|
73
|
+
await self._register_agent(env_vars)
|
74
|
+
else:
|
75
|
+
logger.warning("AGENTEX_BASE_URL not set, skipping agent registration")
|
76
|
+
|
77
|
+
yield
|
78
|
+
|
79
|
+
return lifespan_context
|
80
|
+
|
81
|
+
async def _healthz(self):
|
82
|
+
"""Health check endpoint"""
|
83
|
+
return {"status": "healthy"}
|
84
|
+
|
85
|
+
def _wrap_handler(self, fn: Callable[..., Awaitable[Any]]):
|
86
|
+
"""Wraps handler functions to provide JSON-RPC 2.0 response format"""
|
87
|
+
|
88
|
+
async def wrapper(*args, **kwargs) -> Any:
|
89
|
+
return await fn(*args, **kwargs)
|
90
|
+
|
91
|
+
return wrapper
|
92
|
+
|
93
|
+
async def _handle_jsonrpc(self, request: Request):
|
94
|
+
"""Main JSON-RPC endpoint handler"""
|
95
|
+
rpc_request = None
|
96
|
+
try:
|
97
|
+
data = await request.json()
|
98
|
+
rpc_request = JSONRPCRequest(**data)
|
99
|
+
|
100
|
+
# Check if method is valid first
|
101
|
+
try:
|
102
|
+
method = RPCMethod(rpc_request.method)
|
103
|
+
except ValueError:
|
104
|
+
logger.error(f"Method {rpc_request.method} was invalid")
|
105
|
+
return JSONRPCResponse(
|
106
|
+
id=rpc_request.id,
|
107
|
+
error=JSONRPCError(
|
108
|
+
code=-32601, message=f"Method {rpc_request.method} not found"
|
109
|
+
),
|
110
|
+
)
|
111
|
+
|
112
|
+
if method not in self._handlers or self._handlers[method] is None:
|
113
|
+
logger.error(f"Method {method} not found on existing ACP server")
|
114
|
+
return JSONRPCResponse(
|
115
|
+
id=rpc_request.id,
|
116
|
+
error=JSONRPCError(
|
117
|
+
code=-32601, message=f"Method {method} not found"
|
118
|
+
),
|
119
|
+
)
|
120
|
+
|
121
|
+
# Parse params into appropriate model based on method
|
122
|
+
params_model = PARAMS_MODEL_BY_METHOD[method]
|
123
|
+
params = params_model.model_validate(rpc_request.params)
|
124
|
+
|
125
|
+
if method in RPC_SYNC_METHODS:
|
126
|
+
handler = self._handlers[method]
|
127
|
+
result = await handler(params)
|
128
|
+
|
129
|
+
if rpc_request.id is None:
|
130
|
+
# Seems like you should return None for notifications
|
131
|
+
return None
|
132
|
+
else:
|
133
|
+
# Handle streaming vs non-streaming for MESSAGE_SEND
|
134
|
+
if method == RPCMethod.MESSAGE_SEND and isinstance(
|
135
|
+
result, AsyncGenerator
|
136
|
+
):
|
137
|
+
return await self._handle_streaming_response(
|
138
|
+
rpc_request.id, result
|
139
|
+
)
|
140
|
+
else:
|
141
|
+
if isinstance(result, BaseModel):
|
142
|
+
result = result.model_dump()
|
143
|
+
return JSONRPCResponse(id=rpc_request.id, result=result)
|
144
|
+
else:
|
145
|
+
# If this is a notification (no request ID), process in background and return immediately
|
146
|
+
if rpc_request.id is None:
|
147
|
+
asyncio.create_task(self._process_notification(method, params))
|
148
|
+
return JSONRPCResponse(id=None)
|
149
|
+
|
150
|
+
# For regular requests, start processing in background but return immediately
|
151
|
+
asyncio.create_task(
|
152
|
+
self._process_request(rpc_request.id, method, params)
|
153
|
+
)
|
154
|
+
|
155
|
+
# Return immediate acknowledgment
|
156
|
+
return JSONRPCResponse(
|
157
|
+
id=rpc_request.id, result={"status": "processing"}
|
158
|
+
)
|
159
|
+
|
160
|
+
except Exception as e:
|
161
|
+
logger.error(f"Error handling JSON-RPC request: {e}", exc_info=True)
|
162
|
+
request_id = None
|
163
|
+
if rpc_request is not None:
|
164
|
+
request_id = rpc_request.id
|
165
|
+
return JSONRPCResponse(
|
166
|
+
id=request_id,
|
167
|
+
error=JSONRPCError(code=-32603, message=str(e)).model_dump(),
|
168
|
+
)
|
169
|
+
|
170
|
+
async def _handle_streaming_response(
|
171
|
+
self, request_id: int | str, async_gen: AsyncGenerator
|
172
|
+
):
|
173
|
+
"""Handle streaming response by formatting TaskMessageUpdate objects as JSON-RPC stream"""
|
174
|
+
|
175
|
+
async def generate_json_rpc_stream():
|
176
|
+
try:
|
177
|
+
async for chunk in async_gen:
|
178
|
+
# Each chunk should be a TaskMessageUpdate object
|
179
|
+
# Validate using Pydantic's TypeAdapter to ensure it's a proper TaskMessageUpdate
|
180
|
+
try:
|
181
|
+
# This will validate that chunk conforms to the TaskMessageUpdate union type
|
182
|
+
validated_chunk = task_message_update_adapter.validate_python(
|
183
|
+
chunk
|
184
|
+
)
|
185
|
+
# Use mode="json" to properly serialize datetime objects
|
186
|
+
chunk_data = validated_chunk.model_dump(mode="json")
|
187
|
+
except ValidationError as e:
|
188
|
+
raise TypeError(
|
189
|
+
f"Streaming chunks must be TaskMessageUpdate objects. Validation error: {e}"
|
190
|
+
) from e
|
191
|
+
except Exception as e:
|
192
|
+
raise TypeError(
|
193
|
+
f"Streaming chunks must be TaskMessageUpdate objects, got {type(chunk)}: {e}"
|
194
|
+
) from e
|
195
|
+
|
196
|
+
# Wrap in JSON-RPC response format
|
197
|
+
response = JSONRPCResponse(id=request_id, result=chunk_data)
|
198
|
+
# Use model_dump_json() which handles datetime serialization automatically
|
199
|
+
yield f"{response.model_dump_json()}\n"
|
200
|
+
|
201
|
+
except Exception as e:
|
202
|
+
logger.error(f"Error in streaming response: {e}", exc_info=True)
|
203
|
+
error_response = JSONRPCResponse(
|
204
|
+
id=request_id,
|
205
|
+
error=JSONRPCError(code=-32603, message=str(e)).model_dump(),
|
206
|
+
)
|
207
|
+
yield f"{error_response.model_dump_json()}\n"
|
208
|
+
|
209
|
+
return StreamingResponse(
|
210
|
+
generate_json_rpc_stream(),
|
211
|
+
media_type="application/x-ndjson", # Newline Delimited JSON
|
212
|
+
headers={
|
213
|
+
"Cache-Control": "no-cache",
|
214
|
+
"Connection": "keep-alive",
|
215
|
+
"X-Accel-Buffering": "no", # Disable nginx buffering
|
216
|
+
},
|
217
|
+
)
|
218
|
+
|
219
|
+
async def _process_notification(self, method: RPCMethod, params: Any):
|
220
|
+
"""Process a notification (request with no ID) in the background"""
|
221
|
+
try:
|
222
|
+
handler = self._handlers[method]
|
223
|
+
await handler(params)
|
224
|
+
except Exception as e:
|
225
|
+
logger.error(f"Error processing notification {method}: {e}", exc_info=True)
|
226
|
+
|
227
|
+
async def _process_request(
|
228
|
+
self, request_id: int | str, method: RPCMethod, params: Any
|
229
|
+
):
|
230
|
+
"""Process a request in the background"""
|
231
|
+
try:
|
232
|
+
handler = self._handlers[method]
|
233
|
+
await handler(params)
|
234
|
+
# Note: In a real implementation, you might want to store the result somewhere
|
235
|
+
# or notify the client through a different mechanism
|
236
|
+
logger.info(
|
237
|
+
f"Successfully processed request {request_id} for method {method}"
|
238
|
+
)
|
239
|
+
except Exception as e:
|
240
|
+
logger.error(
|
241
|
+
f"Error processing request {request_id} for method {method}: {e}",
|
242
|
+
exc_info=True,
|
243
|
+
)
|
244
|
+
|
245
|
+
"""
|
246
|
+
Define all possible decorators to be overriden and implemented by each ACP implementation
|
247
|
+
Then the users can override the default handlers by implementing their own handlers
|
248
|
+
|
249
|
+
ACP Type: Agentic
|
250
|
+
Decorators:
|
251
|
+
- on_task_create
|
252
|
+
- on_task_event_send
|
253
|
+
- on_task_cancel
|
254
|
+
|
255
|
+
ACP Type: Sync
|
256
|
+
Decorators:
|
257
|
+
- on_message_send
|
258
|
+
"""
|
259
|
+
|
260
|
+
# Type: Agentic
|
261
|
+
def on_task_create(self, fn: Callable[[CreateTaskParams], Awaitable[Any]]):
|
262
|
+
"""Handle task/init method"""
|
263
|
+
wrapped = self._wrap_handler(fn)
|
264
|
+
self._handlers[RPCMethod.TASK_CREATE] = wrapped
|
265
|
+
return fn
|
266
|
+
|
267
|
+
# Type: Agentic
|
268
|
+
def on_task_event_send(self, fn: Callable[[SendEventParams], Awaitable[Any]]):
|
269
|
+
"""Handle event/send method"""
|
270
|
+
|
271
|
+
async def wrapped_handler(params: SendEventParams):
|
272
|
+
# # # Send message to client first most of the time
|
273
|
+
# ## But, sometimes you may want to process the message first
|
274
|
+
# ## and then send a message to the client
|
275
|
+
# await agentex.interactions.send_messages_to_client(
|
276
|
+
# task_id=params.task_id,
|
277
|
+
# messages=[params.message]
|
278
|
+
# )
|
279
|
+
return await fn(params)
|
280
|
+
|
281
|
+
wrapped = self._wrap_handler(wrapped_handler)
|
282
|
+
self._handlers[RPCMethod.EVENT_SEND] = wrapped
|
283
|
+
return fn
|
284
|
+
|
285
|
+
# Type: Agentic
|
286
|
+
def on_task_cancel(self, fn: Callable[[CancelTaskParams], Awaitable[Any]]):
|
287
|
+
"""Handle task/cancel method"""
|
288
|
+
wrapped = self._wrap_handler(fn)
|
289
|
+
self._handlers[RPCMethod.TASK_CANCEL] = wrapped
|
290
|
+
return fn
|
291
|
+
|
292
|
+
# Type: Sync
|
293
|
+
def on_message_send(
|
294
|
+
self,
|
295
|
+
fn: Callable[
|
296
|
+
[SendMessageParams],
|
297
|
+
Awaitable[TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]],
|
298
|
+
],
|
299
|
+
):
|
300
|
+
"""Handle message/send method - supports both single and streaming responses
|
301
|
+
|
302
|
+
For non-streaming: return a single TaskMessage
|
303
|
+
For streaming: return an AsyncGenerator that yields TaskMessageUpdate objects
|
304
|
+
"""
|
305
|
+
|
306
|
+
async def message_send_wrapper(params: SendMessageParams):
|
307
|
+
"""Special wrapper for message_send that handles both regular async functions and async generators"""
|
308
|
+
# Check if the function is an async generator function
|
309
|
+
|
310
|
+
# Regardless of whether the Agent developer implemented an Async generator or not, we will always turn the function into an async generator and yield SSE events back tot he Agentex server so there is only one way for it to process the response. Then, based on the client's desire to stream or not, the Agentex server will either yield back the async generator objects directly (if streaming) or aggregate the content into a list of TaskMessageContents and to dispatch to the client. This basically gives the Agentex server the flexibility to handle both cases itself.
|
311
|
+
|
312
|
+
if inspect.isasyncgenfunction(fn):
|
313
|
+
# The client wants streaming, an async generator already streams the content, so just return it
|
314
|
+
return fn(params)
|
315
|
+
else:
|
316
|
+
# The client wants streaming, but the function is not an async generator, so we turn it into one and yield each TaskMessageContent as a StreamTaskMessageFull which will be streamed to the client by the Agentex server.
|
317
|
+
task_message_content_response = await fn(params)
|
318
|
+
if isinstance(task_message_content_response, list):
|
319
|
+
task_message_content_list = task_message_content_response
|
320
|
+
else:
|
321
|
+
task_message_content_list = [task_message_content_response]
|
322
|
+
|
323
|
+
async def async_generator(task_message_content_list: list[TaskMessageContent]):
|
324
|
+
for i, task_message_content in enumerate(task_message_content_list):
|
325
|
+
yield StreamTaskMessageFull(index=i, content=task_message_content)
|
326
|
+
|
327
|
+
return async_generator(task_message_content_list)
|
328
|
+
|
329
|
+
self._handlers[RPCMethod.MESSAGE_SEND] = message_send_wrapper
|
330
|
+
return fn
|
331
|
+
|
332
|
+
"""
|
333
|
+
End of Decorators
|
334
|
+
"""
|
335
|
+
|
336
|
+
"""
|
337
|
+
ACP Server Lifecycle Methods
|
338
|
+
"""
|
339
|
+
|
340
|
+
def run(self, host: str = "0.0.0.0", port: int = 8000, **kwargs):
|
341
|
+
"""Start the Uvicorn server for async handlers."""
|
342
|
+
uvicorn.run(self, host=host, port=port, **kwargs)
|
343
|
+
|
344
|
+
async def _register_agent(self, env_vars: EnvironmentVariables):
|
345
|
+
"""Register this agent with the Agentex server"""
|
346
|
+
# Build the agent's own URL
|
347
|
+
full_acp_url = f"{env_vars.ACP_URL.rstrip('/')}:{env_vars.ACP_PORT}"
|
348
|
+
|
349
|
+
description = (
|
350
|
+
env_vars.AGENT_DESCRIPTION
|
351
|
+
or f"Generic description for agent: {env_vars.AGENT_NAME}"
|
352
|
+
)
|
353
|
+
# Prepare registration data
|
354
|
+
registration_data = {
|
355
|
+
"name": env_vars.AGENT_NAME,
|
356
|
+
"description": description,
|
357
|
+
"acp_url": full_acp_url,
|
358
|
+
"acp_type": env_vars.ACP_TYPE,
|
359
|
+
}
|
360
|
+
|
361
|
+
if env_vars.AGENT_ID:
|
362
|
+
registration_data["agent_id"] = env_vars.AGENT_ID
|
363
|
+
|
364
|
+
# Make the registration request
|
365
|
+
registration_url = f"{env_vars.AGENTEX_BASE_URL.rstrip('/')}/agents/register"
|
366
|
+
# Retry logic with configurable attempts and delay
|
367
|
+
max_retries = 3
|
368
|
+
base_delay = 5 # seconds
|
369
|
+
last_exception = None
|
370
|
+
|
371
|
+
attempt = 0
|
372
|
+
while attempt < max_retries:
|
373
|
+
try:
|
374
|
+
async with httpx.AsyncClient() as client:
|
375
|
+
response = await client.post(
|
376
|
+
registration_url, json=registration_data, timeout=30.0
|
377
|
+
)
|
378
|
+
if response.status_code == 200:
|
379
|
+
logger.info(
|
380
|
+
f"Successfully registered agent '{env_vars.AGENT_NAME}' with Agentex server with acp_url: {full_acp_url}. Registration data: {registration_data}"
|
381
|
+
)
|
382
|
+
return # Success, exit the retry loop
|
383
|
+
else:
|
384
|
+
error_msg = f"Failed to register agent. Status: {response.status_code}, Response: {response.text}"
|
385
|
+
logger.error(error_msg)
|
386
|
+
last_exception = Exception(
|
387
|
+
f"Failed to startup agent: {response.text}"
|
388
|
+
)
|
389
|
+
|
390
|
+
except Exception as e:
|
391
|
+
logger.error(
|
392
|
+
f"Exception during agent registration attempt {attempt + 1}: {e}"
|
393
|
+
)
|
394
|
+
last_exception = e
|
395
|
+
attempt += 1
|
396
|
+
if attempt < max_retries:
|
397
|
+
delay = (attempt) * base_delay # 5, 10, 15 seconds
|
398
|
+
logger.info(
|
399
|
+
f"Retrying in {delay} seconds... (attempt {attempt}/{max_retries})"
|
400
|
+
)
|
401
|
+
await asyncio.sleep(delay)
|
402
|
+
|
403
|
+
# If we get here, all retries failed
|
404
|
+
raise last_exception or Exception(
|
405
|
+
f"Failed to register agent after {max_retries} attempts"
|
406
|
+
)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer
|
3
|
+
from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP
|
4
|
+
from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP
|
5
|
+
from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP
|
6
|
+
from agentex.lib.types.fastacp import (
|
7
|
+
AgenticACPConfig,
|
8
|
+
BaseACPConfig,
|
9
|
+
SyncACPConfig,
|
10
|
+
)
|
11
|
+
|
12
|
+
# Add new mappings between ACP types and configs here
|
13
|
+
# Add new mappings between ACP types and implementations here
|
14
|
+
AGENTIC_ACP_IMPLEMENTATIONS: dict[Literal["temporal", "base"], type[BaseACPServer]] = {
|
15
|
+
"temporal": TemporalACP,
|
16
|
+
"base": AgenticBaseACP,
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
class FastACP:
|
21
|
+
"""Factory for creating FastACP instances
|
22
|
+
|
23
|
+
Supports two main ACP types:
|
24
|
+
- "sync": Simple synchronous ACP implementation
|
25
|
+
- "agentic": Advanced ACP with sub-types "base" or "temporal" (requires config)
|
26
|
+
"""
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
# Note: the config is optional and not used right now but is there to be extended in the future
|
30
|
+
def create_sync_acp(config: SyncACPConfig | None = None, **kwargs) -> SyncACP:
|
31
|
+
"""Create a SyncACP instance"""
|
32
|
+
return SyncACP.create(**kwargs)
|
33
|
+
|
34
|
+
@staticmethod
|
35
|
+
def create_agentic_acp(config: AgenticACPConfig, **kwargs) -> BaseACPServer:
|
36
|
+
"""Create an agentic ACP instance (base or temporal)
|
37
|
+
|
38
|
+
Args:
|
39
|
+
config: AgenticACPConfig with type="base" or type="temporal"
|
40
|
+
**kwargs: Additional configuration parameters
|
41
|
+
"""
|
42
|
+
# Get implementation class
|
43
|
+
implementation_class = AGENTIC_ACP_IMPLEMENTATIONS[config.type]
|
44
|
+
# Handle temporal-specific configuration
|
45
|
+
if config.type == "temporal":
|
46
|
+
# Extract temporal_address from config if it's a TemporalACPConfig
|
47
|
+
temporal_config = kwargs.copy()
|
48
|
+
if hasattr(config, "temporal_address"):
|
49
|
+
temporal_config["temporal_address"] = config.temporal_address
|
50
|
+
return implementation_class.create(**temporal_config)
|
51
|
+
else:
|
52
|
+
return implementation_class.create(**kwargs)
|
53
|
+
|
54
|
+
@staticmethod
|
55
|
+
def create(
|
56
|
+
acp_type: Literal["sync", "agentic"], config: BaseACPConfig | None = None, **kwargs
|
57
|
+
) -> BaseACPServer | SyncACP | AgenticBaseACP | TemporalACP:
|
58
|
+
"""Main factory method to create any ACP type
|
59
|
+
|
60
|
+
Args:
|
61
|
+
acp_type: Type of ACP to create ("sync" or "agentic")
|
62
|
+
config: Configuration object. Required for agentic type.
|
63
|
+
**kwargs: Additional configuration parameters
|
64
|
+
"""
|
65
|
+
|
66
|
+
if acp_type == "sync":
|
67
|
+
sync_config = config if isinstance(config, SyncACPConfig) else None
|
68
|
+
return FastACP.create_sync_acp(sync_config, **kwargs)
|
69
|
+
elif acp_type == "agentic":
|
70
|
+
if config is None:
|
71
|
+
config = AgenticACPConfig(type="base")
|
72
|
+
if not isinstance(config, AgenticACPConfig):
|
73
|
+
raise ValueError("AgenticACPConfig is required for agentic ACP type")
|
74
|
+
return FastACP.create_agentic_acp(config, **kwargs)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from typing_extensions import override
|
3
|
+
from agentex import AsyncAgentex
|
4
|
+
from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer
|
5
|
+
from agentex.lib.types.acp import (
|
6
|
+
CancelTaskParams,
|
7
|
+
CreateTaskParams,
|
8
|
+
SendEventParams,
|
9
|
+
)
|
10
|
+
from agentex.lib.utils.logging import make_logger
|
11
|
+
|
12
|
+
logger = make_logger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class AgenticBaseACP(BaseACPServer):
|
16
|
+
"""
|
17
|
+
AgenticBaseACP implementation - a synchronous ACP that provides basic functionality
|
18
|
+
without any special async orchestration like Temporal.
|
19
|
+
|
20
|
+
This implementation provides simple synchronous processing of tasks
|
21
|
+
and is suitable for basic agent implementations.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self):
|
25
|
+
super().__init__()
|
26
|
+
self._setup_handlers()
|
27
|
+
self._agentex_client = AsyncAgentex()
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
@override
|
31
|
+
def create(cls, **kwargs: Any) -> "AgenticBaseACP":
|
32
|
+
"""Create and initialize SyncACP instance
|
33
|
+
|
34
|
+
Args:
|
35
|
+
**kwargs: Configuration parameters (unused in sync implementation)
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
Initialized SyncACP instance
|
39
|
+
"""
|
40
|
+
logger.info("Initializing AgenticBaseACP instance")
|
41
|
+
instance = cls()
|
42
|
+
logger.info("AgenticBaseACP instance initialized with default handlers")
|
43
|
+
return instance
|
44
|
+
|
45
|
+
@override
|
46
|
+
def _setup_handlers(self):
|
47
|
+
"""Set up default handlers for sync operations"""
|
48
|
+
|
49
|
+
@self.on_task_create
|
50
|
+
async def handle_create_task(params: CreateTaskParams) -> None: # type: ignore[unused-function]
|
51
|
+
"""Default create task handler - logs the task"""
|
52
|
+
logger.info(f"AgenticBaseACP creating task {params.task.id}")
|
53
|
+
|
54
|
+
@self.on_task_event_send
|
55
|
+
async def handle_event_send(params: SendEventParams) -> None: # type: ignore[unused-function]
|
56
|
+
"""Default event handler - logs the event"""
|
57
|
+
logger.info(
|
58
|
+
f"AgenticBaseACP received event for task {params.task.id}: {params.event.id},"
|
59
|
+
f"content: {params.event.content}"
|
60
|
+
)
|
61
|
+
# TODO: Implement event handling logic here
|
62
|
+
|
63
|
+
# Implement cursor commit logic here
|
64
|
+
await self._agentex_client.tracker.update(
|
65
|
+
tracker_id=params.task.id,
|
66
|
+
last_processed_event_id=params.event.id,
|
67
|
+
)
|
68
|
+
|
69
|
+
@self.on_task_cancel
|
70
|
+
async def handle_cancel(params: CancelTaskParams) -> None: # type: ignore[unused-function]
|
71
|
+
"""Default cancel handler - logs the cancellation"""
|
72
|
+
logger.info(f"AgenticBaseACP canceling task {params.task.id}")
|
@@ -0,0 +1,109 @@
|
|
1
|
+
from collections.abc import AsyncGenerator
|
2
|
+
from typing import Any, override
|
3
|
+
|
4
|
+
from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer
|
5
|
+
from agentex.lib.types.acp import SendMessageParams
|
6
|
+
from agentex.lib.types.task_message_updates import (
|
7
|
+
StreamTaskMessageDelta,
|
8
|
+
StreamTaskMessageFull,
|
9
|
+
TaskMessageUpdate,
|
10
|
+
TextDelta,
|
11
|
+
)
|
12
|
+
from agentex.types.task_message_content import TaskMessageContent, TextContent
|
13
|
+
from agentex.lib.utils.logging import make_logger
|
14
|
+
|
15
|
+
logger = make_logger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class SyncACP(BaseACPServer):
|
19
|
+
"""
|
20
|
+
SyncACP provides synchronous request-response style communication.
|
21
|
+
Handlers execute and return responses immediately.
|
22
|
+
|
23
|
+
The SyncACP automatically creates input and output messages, so handlers
|
24
|
+
don't need to manually create TaskMessage objects via the Agentex API. All that needs
|
25
|
+
to be done is return the output message via TaskMessageContent objects.
|
26
|
+
|
27
|
+
Usage:
|
28
|
+
acp = SyncACP()
|
29
|
+
|
30
|
+
@acp.on_message_send
|
31
|
+
async def handle_message(params: SendMessageParams) -> TaskMessageContent:
|
32
|
+
# Process message and return response
|
33
|
+
pass
|
34
|
+
|
35
|
+
acp.run()
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self):
|
39
|
+
super().__init__()
|
40
|
+
self._setup_handlers()
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
@override
|
44
|
+
def create(cls, **kwargs: Any) -> "SyncACP":
|
45
|
+
"""Create and initialize SyncACP instance
|
46
|
+
|
47
|
+
Args:
|
48
|
+
**kwargs: Configuration parameters (unused in sync implementation)
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Initialized SyncACP instance
|
52
|
+
"""
|
53
|
+
logger.info("Creating SyncACP instance")
|
54
|
+
instance = cls()
|
55
|
+
logger.info("SyncACP instance created with default handlers")
|
56
|
+
return instance
|
57
|
+
|
58
|
+
@override
|
59
|
+
def _setup_handlers(self):
|
60
|
+
"""Set up default handlers for sync operations"""
|
61
|
+
|
62
|
+
@self.on_message_send
|
63
|
+
async def handle_message_send( # type: ignore[unused-function]
|
64
|
+
params: SendMessageParams
|
65
|
+
) -> TaskMessageContent | AsyncGenerator[TaskMessageUpdate, None]:
|
66
|
+
"""Default message handler with TaskMessageUpdate streaming support
|
67
|
+
|
68
|
+
For streaming, the SyncACP server automatically creates the input and output
|
69
|
+
messages, so we just return TaskMessageUpdate objects with parent_task_message=None
|
70
|
+
"""
|
71
|
+
logger.info(
|
72
|
+
f"SyncACP received message for task {params.task.id}: {params.content}"
|
73
|
+
)
|
74
|
+
|
75
|
+
if params.stream:
|
76
|
+
# Return streaming response
|
77
|
+
async def stream_response():
|
78
|
+
# Example: Stream 3 chunks
|
79
|
+
full_message = ""
|
80
|
+
for i in range(3):
|
81
|
+
data = f"Streaming chunk {i+1}: Processing your request...\n"
|
82
|
+
full_message += data
|
83
|
+
yield StreamTaskMessageDelta(
|
84
|
+
type="delta",
|
85
|
+
index=0,
|
86
|
+
delta=TextDelta(
|
87
|
+
text_delta=f"Streaming chunk {i+1}: Processing your request...\n"
|
88
|
+
),
|
89
|
+
)
|
90
|
+
|
91
|
+
# Final response
|
92
|
+
yield StreamTaskMessageFull(
|
93
|
+
type="full",
|
94
|
+
index=0,
|
95
|
+
content=TextContent(
|
96
|
+
author="agent",
|
97
|
+
content=full_message,
|
98
|
+
format="markdown",
|
99
|
+
),
|
100
|
+
)
|
101
|
+
|
102
|
+
return stream_response()
|
103
|
+
else:
|
104
|
+
# Return single response for non-streaming
|
105
|
+
return TextContent(
|
106
|
+
author="agent",
|
107
|
+
content=f"Processed message for task {params.task.id}",
|
108
|
+
format="markdown",
|
109
|
+
)
|