diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d1eff5521..8032c17e8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.11.4" + ".": "0.12.0" } diff --git a/.stats.yml b/.stats.yml index 1de9197e7..5e5e990c0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 63 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-77ce1b851a8b44f0e67ce2c3ebc20cd92d5af16ab9d3a9224344cd4c83ffb564.yml -openapi_spec_hash: d31d828c46635cbc20165177c7187a70 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-4d4bf80af19e6a2ef6b890d5d978316b86e9939d8d5116e94b90117525c61325.yml +openapi_spec_hash: 133afeacb42000ed4f9e076abf4b50fd config_hash: ba5183ca635940fd202d05a2a9fcb630 diff --git a/CHANGELOG.md b/CHANGELOG.md index 35cf6735e..e5efcbe04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 0.12.0 (2026-05-27) + +Full Changelog: [v0.11.4...v0.12.0](https://github.com/scaleapi/scale-agentex-python/compare/v0.11.4...v0.12.0) + +### Features + +* **api:** add cleaned_at field to task response types ([38ed338](https://github.com/scaleapi/scale-agentex-python/commit/38ed3384094f7f07f6b2482489f457fd1dc4f76d)) + + +### Chores + +* **deps:** drop unused runtime deps and exclude tests from wheel ([#367](https://github.com/scaleapi/scale-agentex-python/issues/367)) ([f4303d1](https://github.com/scaleapi/scale-agentex-python/commit/f4303d1e7211783d19beca6554e44eb73bb29c42)) + + +### Refactors + +* **types:** promote protocol types to agentex.protocol.* ([#371](https://github.com/scaleapi/scale-agentex-python/issues/371)) ([6f1c14f](https://github.com/scaleapi/scale-agentex-python/commit/6f1c14fd61077da52038361642a9fbc4a0a56c8b)) + ## 0.11.4 (2026-05-26) Full Changelog: [v0.11.3...v0.11.4](https://github.com/scaleapi/scale-agentex-python/compare/v0.11.3...v0.11.4) diff --git a/CLAUDE.md b/CLAUDE.md index 7e0df1779..7f62a42fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,11 +55,27 @@ The package provides the `agentex` CLI with these main commands: ### Code Structure - `/src/agentex/` - Core SDK and generated API client code +- `/src/agentex/protocol/` - **Canonical** location for wire-protocol shapes + (JSON-RPC envelopes, ACP method-param types). Depends only on `pydantic` + and the Stainless-generated `agentex.types.*` surface, so it is safe to + import from a future slim REST-only install. + - `acp.py` - `RPCMethod`, `CreateTaskParams`, `SendMessageParams`, + `SendEventParams`, `CancelTaskParams`, `RPC_SYNC_METHODS`, + `PARAMS_MODEL_BY_METHOD` + - `json_rpc.py` - `JSONRPCRequest`, `JSONRPCResponse`, `JSONRPCError` - `/src/agentex/lib/` - Custom library code (not modified by code generator) - `/cli/` - Command-line interface implementation - `/core/` - Core services, adapters, and temporal workflows - `/sdk/` - SDK utilities and FastACP implementation - `/types/` - Custom type definitions + - `acp.py`, `json_rpc.py` - **back-compat shims** re-exporting from + `agentex.protocol.*`. Existing `from agentex.lib.types.{acp,json_rpc} + import ...` keeps working; new code should import from the canonical + `agentex.protocol.*` paths. + - Other modules (`tracing`, `agent_card`, `credentials`, `fastacp`, + `llm_messages`, `converters`, etc.) stay here — they have heavier + transitive deps (temporal, openai-agents, model_utils/yaml) and + aren't slim-safe. - `/utils/` - Utility functions - `/examples/` - Example implementations and tutorials - `/tests/` - Test suites diff --git a/pyproject.toml b/pyproject.toml index bc210cace..dc6705ac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agentex-sdk" -version = "0.11.4" +version = "0.12.0" description = "The official Python library for the agentex API" dynamic = ["readme"] license = "Apache-2.0" @@ -20,7 +20,6 @@ dependencies = [ "rich>=13.9.2,<14", "fastapi>=0.115.0", "starlette>=0.49.1", - "tornado>=6.5.5", "uvicorn>=0.31.1", "watchfiles>=0.24.0,<1.0", "python-on-whales>=0.73.0,<0.74", @@ -33,24 +32,17 @@ dependencies = [ "litellm>=1.83.7,<2", "kubernetes>=25.0.0,<36.0.0", "jinja2>=3.1.3,<4", - "mcp[cli]>=1.4.1", + "mcp>=1.4.1", "scale-gp>=0.1.0a59", "openai-agents==0.14.1", "pydantic-ai-slim>=1.0,<2", - "tzlocal>=5.3.1", - "tzdata>=2025.2", - "pytest>=8.4.0", "json_log_formatter>=1.1.1", - "pytest-asyncio>=1.0.0", "scale-gp-beta>=0.2.0", - "ipykernel>=6.29.5", "openai>=2.2,<3", # Required by openai-agents; litellm now supports openai 2.x (issue #13711 resolved: https://github.com/BerriAI/litellm/issues/13711) "cloudpickle>=3.1.1", - "datadog>=0.52.1", "ddtrace>=3.13.0", "yaspin>=3.1.0", "claude-agent-sdk>=0.1.0", - "anthropic>=0.40.0", "langgraph-checkpoint>=2.0.0", "opentelemetry-sdk>=1.20.0", "opentelemetry-api>=1.20.0", @@ -152,6 +144,14 @@ include = [ [tool.hatch.build.targets.wheel] packages = ["src/agentex"] +# Don't ship internal test files in the wheel. `lib/cli/templates/**/test_agent.py.j2` +# is intentionally kept — those render into user projects. +exclude = [ + "src/agentex/lib/**/tests/**", + "src/agentex/lib/**/test_*.py", + "src/agentex/lib/**/conftest.py", + "src/agentex/lib/**/pytest.ini", +] [tool.hatch.build.targets.sdist] # Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc) diff --git a/src/agentex/_version.py b/src/agentex/_version.py index 4efc70f7f..80a336119 100644 --- a/src/agentex/_version.py +++ b/src/agentex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "agentex" -__version__ = "0.11.4" # x-release-please-version +__version__ = "0.12.0" # x-release-please-version diff --git a/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 b/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 index 6099f5c3e..3309dc07e 100644 --- a/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 @@ -18,7 +18,7 @@ import agentex.lib.adk as adk from agentex.lib.adk import create_langgraph_tracing_handler, stream_langgraph_events from agentex.lib.core.tracing.tracing_processor_manager import add_tracing_processor_config from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams +from agentex.protocol.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AsyncACPConfig from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger diff --git a/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 b/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 index b63683da1..5692396b2 100644 --- a/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 @@ -28,7 +28,7 @@ from agentex.lib.adk import ( stream_pydantic_ai_events, create_pydantic_ai_tracing_handler, ) -from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams +from agentex.protocol.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AsyncACPConfig from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger diff --git a/src/agentex/lib/cli/templates/default/project/acp.py.j2 b/src/agentex/lib/cli/templates/default/project/acp.py.j2 index 5478b51b5..b0da14a5c 100644 --- a/src/agentex/lib/cli/templates/default/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default/project/acp.py.j2 @@ -1,6 +1,6 @@ from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.fastacp import AsyncACPConfig -from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams +from agentex.protocol.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent from agentex.lib import adk diff --git a/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 index 2edd20fcb..54538d0c9 100644 --- a/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 @@ -11,7 +11,7 @@ import agentex.lib.adk as adk from agentex.lib.adk import create_langgraph_tracing_handler, convert_langgraph_to_agentex_events from agentex.lib.core.tracing.tracing_processor_manager import add_tracing_processor_config from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import SendMessageParams +from agentex.protocol.acp import SendMessageParams from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger from agentex.types.task_message_content import TaskMessageContent diff --git a/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 index 0b3b482fe..4e2517838 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 @@ -4,7 +4,7 @@ from typing import AsyncGenerator, List from agentex.lib import adk from agentex.lib.adk.providers._modules.sync_provider import SyncStreamingProvider, convert_openai_to_agentex_events from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import SendMessageParams +from agentex.protocol.acp import SendMessageParams from agentex.lib.core.tracing.tracing_processor_manager import add_tracing_processor_config from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.model_utils import BaseModel diff --git a/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 index e07f57a1a..4925e847f 100644 --- a/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 @@ -22,7 +22,7 @@ from agentex.lib.adk import ( create_pydantic_ai_tracing_handler, convert_pydantic_ai_to_agentex_events, ) -from agentex.lib.types.acp import SendMessageParams +from agentex.protocol.acp import SendMessageParams from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger from agentex.lib.sdk.fastacp.fastacp import FastACP diff --git a/src/agentex/lib/cli/templates/sync/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync/project/acp.py.j2 index 7184d26aa..ce5069a4c 100644 --- a/src/agentex/lib/cli/templates/sync/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync/project/acp.py.j2 @@ -1,6 +1,6 @@ from typing import AsyncGenerator, Union from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import SendMessageParams +from agentex.protocol.acp import SendMessageParams from agentex.types.task_message_update import TaskMessageUpdate from agentex.types.task_message_content import TaskMessageContent diff --git a/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 index 5b95d4479..2b81bb335 100644 --- a/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 @@ -4,7 +4,7 @@ import os from temporalio import workflow from agentex.lib import adk -from agentex.lib.types.acp import CreateTaskParams, SendEventParams +from agentex.protocol.acp import CreateTaskParams, SendEventParams from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow from agentex.lib.core.temporal.types.workflow import SignalName from agentex.lib.utils.logging import make_logger diff --git a/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 index 23e5156f1..66a91d7a8 100644 --- a/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 @@ -23,7 +23,7 @@ from temporalio import workflow from project.agent import TaskDeps, temporal_agent from agentex.lib import adk -from agentex.lib.types.acp import SendEventParams, CreateTaskParams +from agentex.protocol.acp import SendEventParams, CreateTaskParams from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent diff --git a/src/agentex/lib/cli/templates/temporal/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal/project/workflow.py.j2 index ad756eb15..56db5abf3 100644 --- a/src/agentex/lib/cli/templates/temporal/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal/project/workflow.py.j2 @@ -3,7 +3,7 @@ import json from temporalio import workflow from agentex.lib import adk -from agentex.lib.types.acp import CreateTaskParams, SendEventParams +from agentex.protocol.acp import CreateTaskParams, SendEventParams from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow from agentex.lib.core.temporal.types.workflow import SignalName from agentex.lib.utils.logging import make_logger diff --git a/src/agentex/lib/core/temporal/services/temporal_task_service.py b/src/agentex/lib/core/temporal/services/temporal_task_service.py index 81eb22389..9d66c0c1f 100644 --- a/src/agentex/lib/core/temporal/services/temporal_task_service.py +++ b/src/agentex/lib/core/temporal/services/temporal_task_service.py @@ -6,7 +6,7 @@ from agentex.types.task import Task from agentex.types.agent import Agent from agentex.types.event import Event -from agentex.lib.types.acp import SendEventParams, CreateTaskParams +from agentex.protocol.acp import SendEventParams, CreateTaskParams from agentex.lib.environment_variables import EnvironmentVariables from agentex.lib.core.clients.temporal.types import WorkflowState from agentex.lib.core.temporal.types.workflow import SignalName diff --git a/src/agentex/lib/core/temporal/workflows/workflow.py b/src/agentex/lib/core/temporal/workflows/workflow.py index 727f3ac85..3e4498162 100644 --- a/src/agentex/lib/core/temporal/workflows/workflow.py +++ b/src/agentex/lib/core/temporal/workflows/workflow.py @@ -2,7 +2,7 @@ from temporalio import workflow -from agentex.lib.types.acp import SendEventParams, CreateTaskParams +from agentex.protocol.acp import SendEventParams, CreateTaskParams from agentex.lib.utils.logging import make_logger from agentex.lib.core.temporal.types.workflow import SignalName diff --git a/src/agentex/lib/sdk/fastacp/base/base_acp_server.py b/src/agentex/lib/sdk/fastacp/base/base_acp_server.py index c48816020..b0b1c3685 100644 --- a/src/agentex/lib/sdk/fastacp/base/base_acp_server.py +++ b/src/agentex/lib/sdk/fastacp/base/base_acp_server.py @@ -14,7 +14,7 @@ from starlette.types import Send, Scope, ASGIApp, Receive from fastapi.responses import StreamingResponse -from agentex.lib.types.acp import ( +from agentex.protocol.acp import ( RPC_SYNC_METHODS, PARAMS_MODEL_BY_METHOD, RPCMethod, @@ -24,7 +24,7 @@ SendMessageParams, ) from agentex.lib.utils.logging import make_logger, ctx_var_request_id -from agentex.lib.types.json_rpc import JSONRPCError, JSONRPCRequest, JSONRPCResponse +from agentex.protocol.json_rpc import JSONRPCError, JSONRPCRequest, JSONRPCResponse from agentex.lib.utils.model_utils import BaseModel from agentex.lib.utils.registration import register_agent diff --git a/src/agentex/lib/sdk/fastacp/impl/async_base_acp.py b/src/agentex/lib/sdk/fastacp/impl/async_base_acp.py index d2b8bac92..e9d20f150 100644 --- a/src/agentex/lib/sdk/fastacp/impl/async_base_acp.py +++ b/src/agentex/lib/sdk/fastacp/impl/async_base_acp.py @@ -1,7 +1,7 @@ from typing import Any from typing_extensions import override -from agentex.lib.types.acp import ( +from agentex.protocol.acp import ( SendEventParams, CancelTaskParams, CreateTaskParams, diff --git a/src/agentex/lib/sdk/fastacp/impl/sync_acp.py b/src/agentex/lib/sdk/fastacp/impl/sync_acp.py index 4898a9637..5ecad073e 100644 --- a/src/agentex/lib/sdk/fastacp/impl/sync_acp.py +++ b/src/agentex/lib/sdk/fastacp/impl/sync_acp.py @@ -3,7 +3,7 @@ from typing import Any, override from collections.abc import AsyncGenerator -from agentex.lib.types.acp import SendMessageParams +from agentex.protocol.acp import SendMessageParams from agentex.lib.utils.logging import make_logger from agentex.types.task_message_delta import TextDelta from agentex.types.task_message_update import ( diff --git a/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py b/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py index f64e16d72..54fe72e6c 100644 --- a/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py +++ b/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py @@ -6,7 +6,7 @@ from fastapi import FastAPI from temporalio.converter import PayloadCodec -from agentex.lib.types.acp import ( +from agentex.protocol.acp import ( SendEventParams, CancelTaskParams, CreateTaskParams, diff --git a/src/agentex/lib/sdk/fastacp/tests/conftest.py b/src/agentex/lib/sdk/fastacp/tests/conftest.py index 8941f16eb..59ecbfee3 100644 --- a/src/agentex/lib/sdk/fastacp/tests/conftest.py +++ b/src/agentex/lib/sdk/fastacp/tests/conftest.py @@ -13,12 +13,12 @@ from agentex.types.task import Task from agentex.types.agent import Agent -from agentex.lib.types.acp import ( +from agentex.protocol.acp import ( CancelTaskParams, CreateTaskParams, SendMessageParams, ) -from agentex.lib.types.json_rpc import JSONRPCRequest +from agentex.protocol.json_rpc import JSONRPCRequest from agentex.types.task_message import TaskMessageContent from agentex.types.task_message_content import TextContent from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP diff --git a/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py b/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py index 0816ac436..8a218187e 100644 --- a/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +++ b/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py @@ -5,7 +5,7 @@ import pytest from fastapi.testclient import TestClient -from agentex.lib.types.acp import ( +from agentex.protocol.acp import ( RPCMethod, SendEventParams, CancelTaskParams, diff --git a/src/agentex/lib/sdk/fastacp/tests/test_integration.py b/src/agentex/lib/sdk/fastacp/tests/test_integration.py index c6f310af1..c72d336e3 100644 --- a/src/agentex/lib/sdk/fastacp/tests/test_integration.py +++ b/src/agentex/lib/sdk/fastacp/tests/test_integration.py @@ -5,7 +5,7 @@ import httpx import pytest -from agentex.lib.types.acp import ( +from agentex.protocol.acp import ( RPCMethod, SendEventParams, CancelTaskParams, diff --git a/src/agentex/lib/types/acp.py b/src/agentex/lib/types/acp.py index d719b4fd5..74295ef88 100644 --- a/src/agentex/lib/types/acp.py +++ b/src/agentex/lib/types/acp.py @@ -1,116 +1,15 @@ -from __future__ import annotations - -from enum import Enum -from typing import Any - -from pydantic import Field, BaseModel - -from agentex.types.task import Task -from agentex.types.agent import Agent -from agentex.types.event import Event -from agentex.types.task_message_content import TaskMessageContent - - -class RPCMethod(str, Enum): - """Available JSON-RPC methods for agent communication.""" - - EVENT_SEND = "event/send" - MESSAGE_SEND = "message/send" - TASK_CANCEL = "task/cancel" - TASK_CREATE = "task/create" - - -class CreateTaskParams(BaseModel): - """Parameters for task/create method. - - Attributes: - agent: The agent that the task was sent to. - task: The task to be created. - params: The parameters for the task as inputted by the user. - request: Additional request context including headers forwarded to this agent. - """ - - agent: Agent = Field(..., description="The agent that the task was sent to") - task: Task = Field(..., description="The task to be created") - params: dict[str, Any] | None = Field( - None, - description="The parameters for the task as inputted by the user", - ) - request: dict[str, Any] | None = Field( - default=None, - description="Additional request context including headers forwarded to this agent", - ) - - -class SendMessageParams(BaseModel): - """Parameters for message/send method. - - Attributes: - agent: The agent that the message was sent to. - task: The task that the message was sent to. - content: The message that was sent to the agent. - stream: Whether to stream the message back to the agentex server from the agent. - request: Additional request context including headers forwarded to this agent. - """ - - agent: Agent = Field(..., description="The agent that the message was sent to") - task: Task = Field(..., description="The task that the message was sent to") - content: TaskMessageContent = Field( - ..., description="The message that was sent to the agent" - ) - stream: bool = Field( - False, - description="Whether to stream the message back to the agentex server from the agent", - ) - request: dict[str, Any] | None = Field( - default=None, - description="Additional request context including headers forwarded to this agent", - ) - - -class SendEventParams(BaseModel): - """Parameters for event/send method. - - Attributes: - agent: The agent that the event was sent to. - task: The task that the message was sent to. - event: The event that was sent to the agent. - request: Additional request context including headers forwarded to this agent. - """ - - agent: Agent = Field(..., description="The agent that the event was sent to") - task: Task = Field(..., description="The task that the message was sent to") - event: Event = Field(..., description="The event that was sent to the agent") - request: dict[str, Any] | None = Field( - default=None, - description="Additional request context including headers forwarded to this agent", - ) - - -class CancelTaskParams(BaseModel): - """Parameters for task/cancel method. - - Attributes: - agent: The agent that the task was sent to. - task: The task that was cancelled. - request: Additional request context including headers forwarded to this agent. - """ - - agent: Agent = Field(..., description="The agent that the task was sent to") - task: Task = Field(..., description="The task that was cancelled") - request: dict[str, Any] | None = Field( - default=None, - description="Additional request context including headers forwarded to this agent", - ) - - -RPC_SYNC_METHODS = [ - RPCMethod.MESSAGE_SEND, -] - -PARAMS_MODEL_BY_METHOD: dict[RPCMethod, type[BaseModel]] = { - RPCMethod.EVENT_SEND: SendEventParams, - RPCMethod.TASK_CANCEL: CancelTaskParams, - RPCMethod.MESSAGE_SEND: SendMessageParams, - RPCMethod.TASK_CREATE: CreateTaskParams, -} +"""Back-compat shim. The canonical location is :mod:`agentex.protocol.acp`. + +Kept here so existing ``from agentex.lib.types.acp import ...`` imports +continue to work. New code should import from the canonical path. +""" + +from agentex.protocol.acp import ( # noqa: F401 + RPC_SYNC_METHODS, + PARAMS_MODEL_BY_METHOD, + RPCMethod, + SendEventParams, + CancelTaskParams, + CreateTaskParams, + SendMessageParams, +) diff --git a/src/agentex/lib/types/json_rpc.py b/src/agentex/lib/types/json_rpc.py index b89e9d6b2..b010f93f7 100644 --- a/src/agentex/lib/types/json_rpc.py +++ b/src/agentex/lib/types/json_rpc.py @@ -1,51 +1,11 @@ -from __future__ import annotations +"""Back-compat shim. The canonical location is :mod:`agentex.protocol.json_rpc`. -from typing import Any, Literal +Kept here so existing ``from agentex.lib.types.json_rpc import ...`` imports +continue to work. New code should import from the canonical path. +""" -from agentex.lib.utils.model_utils import BaseModel - - -class JSONRPCError(BaseModel): - """JSON-RPC 2.0 Error - - Attributes: - code: The error code - message: The error message - data: The error data - """ - - code: int - message: str - data: Any | None = None - - -class JSONRPCRequest(BaseModel): - """JSON-RPC 2.0 Request - - Attributes: - jsonrpc: The JSON-RPC version - method: The method to call - params: The parameters for the request - id: The ID of the request - """ - - jsonrpc: Literal["2.0"] = "2.0" - method: str - params: dict[str, Any] - id: int | str | None = None - - -class JSONRPCResponse(BaseModel): - """JSON-RPC 2.0 Response - - Attributes: - jsonrpc: The JSON-RPC version - result: The result of the request - error: The error of the request - id: The ID of the request - """ - - jsonrpc: Literal["2.0"] = "2.0" - result: dict[str, Any] | None = None - error: JSONRPCError | None = None - id: int | str | None = None +from agentex.protocol.json_rpc import ( # noqa: F401 + JSONRPCError, + JSONRPCRequest, + JSONRPCResponse, +) diff --git a/src/agentex/protocol/__init__.py b/src/agentex/protocol/__init__.py new file mode 100644 index 000000000..be9db981a --- /dev/null +++ b/src/agentex/protocol/__init__.py @@ -0,0 +1,16 @@ +"""Wire-protocol shapes for Agentex. + +The modules under `agentex.protocol.*` are the typed shapes for talking to +an Agentex agent over JSON-RPC (the ACP / Agent Communication Protocol) +without pulling in the heavy ADK runtime. They depend only on pydantic and +the Stainless-generated `agentex.types.*` surface, so they are safe to +import from a slim REST-only install. + +Hand-rolled JSON-RPC clients (e.g. the one in `egp-api-backend`) can switch +from constructing `{"jsonrpc": "2.0", "method": "...", "params": {...}}` +dicts by hand to constructing `JSONRPCRequest(method=RPCMethod.TASK_CREATE, +params=CreateTaskParams(...).model_dump())`. + +For back-compat, the same classes are re-exported from +`agentex.lib.types.{acp,json_rpc}` (the historical locations). +""" diff --git a/src/agentex/protocol/acp.py b/src/agentex/protocol/acp.py new file mode 100644 index 000000000..d719b4fd5 --- /dev/null +++ b/src/agentex/protocol/acp.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +from enum import Enum +from typing import Any + +from pydantic import Field, BaseModel + +from agentex.types.task import Task +from agentex.types.agent import Agent +from agentex.types.event import Event +from agentex.types.task_message_content import TaskMessageContent + + +class RPCMethod(str, Enum): + """Available JSON-RPC methods for agent communication.""" + + EVENT_SEND = "event/send" + MESSAGE_SEND = "message/send" + TASK_CANCEL = "task/cancel" + TASK_CREATE = "task/create" + + +class CreateTaskParams(BaseModel): + """Parameters for task/create method. + + Attributes: + agent: The agent that the task was sent to. + task: The task to be created. + params: The parameters for the task as inputted by the user. + request: Additional request context including headers forwarded to this agent. + """ + + agent: Agent = Field(..., description="The agent that the task was sent to") + task: Task = Field(..., description="The task to be created") + params: dict[str, Any] | None = Field( + None, + description="The parameters for the task as inputted by the user", + ) + request: dict[str, Any] | None = Field( + default=None, + description="Additional request context including headers forwarded to this agent", + ) + + +class SendMessageParams(BaseModel): + """Parameters for message/send method. + + Attributes: + agent: The agent that the message was sent to. + task: The task that the message was sent to. + content: The message that was sent to the agent. + stream: Whether to stream the message back to the agentex server from the agent. + request: Additional request context including headers forwarded to this agent. + """ + + agent: Agent = Field(..., description="The agent that the message was sent to") + task: Task = Field(..., description="The task that the message was sent to") + content: TaskMessageContent = Field( + ..., description="The message that was sent to the agent" + ) + stream: bool = Field( + False, + description="Whether to stream the message back to the agentex server from the agent", + ) + request: dict[str, Any] | None = Field( + default=None, + description="Additional request context including headers forwarded to this agent", + ) + + +class SendEventParams(BaseModel): + """Parameters for event/send method. + + Attributes: + agent: The agent that the event was sent to. + task: The task that the message was sent to. + event: The event that was sent to the agent. + request: Additional request context including headers forwarded to this agent. + """ + + agent: Agent = Field(..., description="The agent that the event was sent to") + task: Task = Field(..., description="The task that the message was sent to") + event: Event = Field(..., description="The event that was sent to the agent") + request: dict[str, Any] | None = Field( + default=None, + description="Additional request context including headers forwarded to this agent", + ) + + +class CancelTaskParams(BaseModel): + """Parameters for task/cancel method. + + Attributes: + agent: The agent that the task was sent to. + task: The task that was cancelled. + request: Additional request context including headers forwarded to this agent. + """ + + agent: Agent = Field(..., description="The agent that the task was sent to") + task: Task = Field(..., description="The task that was cancelled") + request: dict[str, Any] | None = Field( + default=None, + description="Additional request context including headers forwarded to this agent", + ) + + +RPC_SYNC_METHODS = [ + RPCMethod.MESSAGE_SEND, +] + +PARAMS_MODEL_BY_METHOD: dict[RPCMethod, type[BaseModel]] = { + RPCMethod.EVENT_SEND: SendEventParams, + RPCMethod.TASK_CANCEL: CancelTaskParams, + RPCMethod.MESSAGE_SEND: SendMessageParams, + RPCMethod.TASK_CREATE: CreateTaskParams, +} diff --git a/src/agentex/protocol/json_rpc.py b/src/agentex/protocol/json_rpc.py new file mode 100644 index 000000000..be03a4936 --- /dev/null +++ b/src/agentex/protocol/json_rpc.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict + +# Preserve the config the previous `agentex.lib.utils.model_utils.BaseModel` +# applied — `from_attributes=True` lets callers `model_validate` from +# attribute-bearing objects (not just dicts); `populate_by_name=True` is a +# harmless default future-proofing for any field aliases. +_PROTOCOL_MODEL_CONFIG = ConfigDict(from_attributes=True, populate_by_name=True) + + +class JSONRPCError(BaseModel): + """JSON-RPC 2.0 Error + + Attributes: + code: The error code + message: The error message + data: The error data + """ + + model_config = _PROTOCOL_MODEL_CONFIG + + code: int + message: str + data: Any | None = None + + +class JSONRPCRequest(BaseModel): + """JSON-RPC 2.0 Request + + Attributes: + jsonrpc: The JSON-RPC version + method: The method to call + params: The parameters for the request + id: The ID of the request + """ + + model_config = _PROTOCOL_MODEL_CONFIG + + jsonrpc: Literal["2.0"] = "2.0" + method: str + params: dict[str, Any] + id: int | str | None = None + + +class JSONRPCResponse(BaseModel): + """JSON-RPC 2.0 Response + + Attributes: + jsonrpc: The JSON-RPC version + result: The result of the request + error: The error of the request + id: The ID of the request + """ + + model_config = _PROTOCOL_MODEL_CONFIG + + jsonrpc: Literal["2.0"] = "2.0" + result: dict[str, Any] | None = None + error: JSONRPCError | None = None + id: int | str | None = None diff --git a/src/agentex/types/task.py b/src/agentex/types/task.py index 8b9cde47f..3769d5128 100644 --- a/src/agentex/types/task.py +++ b/src/agentex/types/task.py @@ -12,6 +12,8 @@ class Task(BaseModel): id: str + cleaned_at: Optional[datetime] = None + created_at: Optional[datetime] = None name: Optional[str] = None diff --git a/src/agentex/types/task_list_response.py b/src/agentex/types/task_list_response.py index 85f87c0e5..b8d0ab73d 100644 --- a/src/agentex/types/task_list_response.py +++ b/src/agentex/types/task_list_response.py @@ -17,6 +17,8 @@ class TaskListResponseItem(BaseModel): agents: Optional[List[Agent]] = None + cleaned_at: Optional[datetime] = None + created_at: Optional[datetime] = None name: Optional[str] = None diff --git a/src/agentex/types/task_retrieve_by_name_response.py b/src/agentex/types/task_retrieve_by_name_response.py index 1ffc6819a..557764d04 100644 --- a/src/agentex/types/task_retrieve_by_name_response.py +++ b/src/agentex/types/task_retrieve_by_name_response.py @@ -17,6 +17,8 @@ class TaskRetrieveByNameResponse(BaseModel): agents: Optional[List[Agent]] = None + cleaned_at: Optional[datetime] = None + created_at: Optional[datetime] = None name: Optional[str] = None diff --git a/src/agentex/types/task_retrieve_response.py b/src/agentex/types/task_retrieve_response.py index 5f2629468..bb1398d67 100644 --- a/src/agentex/types/task_retrieve_response.py +++ b/src/agentex/types/task_retrieve_response.py @@ -17,6 +17,8 @@ class TaskRetrieveResponse(BaseModel): agents: Optional[List[Agent]] = None + cleaned_at: Optional[datetime] = None + created_at: Optional[datetime] = None name: Optional[str] = None diff --git a/tests/test_header_forwarding.py b/tests/test_header_forwarding.py index 51c3a685f..596c6729c 100644 --- a/tests/test_header_forwarding.py +++ b/tests/test_header_forwarding.py @@ -46,7 +46,7 @@ class _StubTracer(_StubAsyncTracer): from agentex.lib.core.services.adk.acp.acp import ACPService from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer -from agentex.lib.types.acp import RPCMethod, SendMessageParams, SendEventParams +from agentex.protocol.acp import RPCMethod, SendMessageParams, SendEventParams from agentex.types.task_message_content import TextContent from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP from agentex.lib.core.temporal.services.temporal_task_service import TemporalTaskService diff --git a/tests/test_protocol_shims.py b/tests/test_protocol_shims.py new file mode 100644 index 000000000..e5e651b68 --- /dev/null +++ b/tests/test_protocol_shims.py @@ -0,0 +1,98 @@ +"""Tests that pin the back-compat contract for protocol-type shims. + +The canonical location for wire-protocol shapes is :mod:`agentex.protocol` +(see PR scaleapi/scale-agentex-python#371). The historical locations +:mod:`agentex.lib.types.acp` and :mod:`agentex.lib.types.json_rpc` are +preserved as re-export shims so external consumers' existing imports +continue to work. + +These tests enforce two invariants: + +1. **Symbol parity** — every public name the original modules exported + is still importable from the old path. Greptile flagged + ``RPC_SYNC_METHODS`` and ``PARAMS_MODEL_BY_METHOD`` as missing in an + earlier pass; this test prevents that regression. +2. **Identity** — the class objects at the shim path are the *same* + objects as the canonical path. Without this, type-narrowing via + ``isinstance`` or pattern matching would silently misbehave for code + that mixes import styles. + +Also asserts the :class:`pydantic.ConfigDict` settings on the JSON-RPC +classes survived the move from :mod:`agentex.lib.utils.model_utils` to +plain :mod:`pydantic` — Greptile flagged the silent loss of +``from_attributes=True`` / ``populate_by_name=True``. +""" + +from __future__ import annotations + + +def test_acp_shim_re_exports_all_original_symbols() -> None: + """Every name historically exported from agentex.lib.types.acp must + still be importable from that path via the back-compat shim.""" + # Importing each symbol; ImportError here means the shim regressed. + from agentex.lib.types.acp import ( # noqa: F401 + RPC_SYNC_METHODS, + PARAMS_MODEL_BY_METHOD, + RPCMethod, + SendEventParams, + CancelTaskParams, + CreateTaskParams, + SendMessageParams, + ) + + +def test_json_rpc_shim_re_exports_all_original_symbols() -> None: + """Every name historically exported from agentex.lib.types.json_rpc + must still be importable from that path via the back-compat shim.""" + from agentex.lib.types.json_rpc import ( # noqa: F401 + JSONRPCError, + JSONRPCRequest, + JSONRPCResponse, + ) + + +def test_acp_shim_classes_are_identical_to_canonical() -> None: + """Shim re-exports must be the *same* class objects as the canonical + path. Different objects would break ``isinstance`` for code that + mixes import styles.""" + from agentex.protocol import acp as canon + from agentex.lib.types import acp as shim + + assert shim.RPCMethod is canon.RPCMethod + assert shim.CreateTaskParams is canon.CreateTaskParams + assert shim.SendMessageParams is canon.SendMessageParams + assert shim.SendEventParams is canon.SendEventParams + assert shim.CancelTaskParams is canon.CancelTaskParams + assert shim.RPC_SYNC_METHODS is canon.RPC_SYNC_METHODS + assert shim.PARAMS_MODEL_BY_METHOD is canon.PARAMS_MODEL_BY_METHOD + + +def test_json_rpc_shim_classes_are_identical_to_canonical() -> None: + """Same identity check for the JSON-RPC envelope types.""" + from agentex.protocol import json_rpc as canon + from agentex.lib.types import json_rpc as shim + + assert shim.JSONRPCError is canon.JSONRPCError + assert shim.JSONRPCRequest is canon.JSONRPCRequest + assert shim.JSONRPCResponse is canon.JSONRPCResponse + + +def test_json_rpc_classes_preserve_legacy_model_config() -> None: + """Pre-refactor, JSON-RPC classes inherited + ``from_attributes=True`` / ``populate_by_name=True`` from + ``agentex.lib.utils.model_utils.BaseModel``. The refactor swapped + to plain ``pydantic.BaseModel`` and set ``model_config`` explicitly + to preserve both flags. Catch any future drop.""" + from agentex.protocol.json_rpc import ( + JSONRPCError, + JSONRPCRequest, + JSONRPCResponse, + ) + + for cls in (JSONRPCError, JSONRPCRequest, JSONRPCResponse): + assert cls.model_config.get("from_attributes") is True, ( + f"{cls.__name__}.model_config dropped from_attributes=True" + ) + assert cls.model_config.get("populate_by_name") is True, ( + f"{cls.__name__}.model_config dropped populate_by_name=True" + )