From 284537209a61d3a4fd4a7b60971db425013562bc Mon Sep 17 00:00:00 2001 From: "helen@cloud" Date: Thu, 2 Apr 2026 01:19:13 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BA=20AgentCard=20=E4=B8=8E=20ski?= =?UTF-8?q?lls=20=E6=98=BE=E5=BC=8F=E5=A3=B0=E6=98=8E=20inputModes=20/=20o?= =?UTF-8?q?utputModes=20(Closes=20#362)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/opencode_a2a/server/agent_card.py | 20 +++++++++++++++----- tests/server/test_agent_card.py | 11 +++++++++-- tests/server/test_transport_contract.py | 8 +++++++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/opencode_a2a/server/agent_card.py b/src/opencode_a2a/server/agent_card.py index 26cfd65..348b03b 100644 --- a/src/opencode_a2a/server/agent_card.py +++ b/src/opencode_a2a/server/agent_card.py @@ -12,6 +12,7 @@ SecurityScheme, TransportProtocol, ) +from pydantic import Field from ..config import Settings from ..contracts.extensions import ( @@ -47,6 +48,13 @@ _JSON_RPC_MODES = ["application/json"] +class OpencodeAgentCard(AgentCard): + """Custom AgentCard that explicitly includes top-level inputModes and outputModes.""" + + input_modes: list[str] | None = Field(default=None, alias="inputModes") + output_modes: list[str] | None = Field(default=None, alias="outputModes") + + def _select_public_extension_params( params: dict[str, Any], *, @@ -396,8 +404,8 @@ def _build_agent_skills( "Inspect OpenCode session status, history, and low-risk lifecycle actions " "through provider-private JSON-RPC extensions." ), - input_modes=list(_JSON_RPC_MODES), - output_modes=list(_JSON_RPC_MODES), + input_modes=["application/json", "text/plain", "application/octet-stream"], + output_modes=["application/json", "text/plain"], tags=["opencode", "sessions", "history", "provider-private"], ), AgentSkill( @@ -467,8 +475,8 @@ def _build_agent_skills( "provider-private OpenCode session/history and session-control surface " "exposed through JSON-RPC extensions." ), - input_modes=list(_JSON_RPC_MODES), - output_modes=list(_JSON_RPC_MODES), + input_modes=["application/json", "text/plain", "application/octet-stream"], + output_modes=["application/json", "text/plain"], tags=["opencode", "sessions", "history", "provider-private"], examples=_build_session_query_skill_examples( capability_snapshot=capability_snapshot, @@ -551,7 +559,7 @@ def _build_agent_card( security: list[dict[str, list[str]]] = [{"bearerAuth": []}] capability_snapshot = build_capability_snapshot(runtime_profile=runtime_profile) - return AgentCard( + return OpencodeAgentCard( name=settings.a2a_title, description=_build_agent_card_description( settings, @@ -565,6 +573,8 @@ def _build_agent_card( preferred_transport=TransportProtocol.http_json, default_input_modes=list(_CHAT_INPUT_MODES), default_output_modes=list(_CHAT_OUTPUT_MODES), + inputModes=list(_CHAT_INPUT_MODES), + outputModes=list(_CHAT_OUTPUT_MODES), capabilities=AgentCapabilities( streaming=True, extensions=_build_agent_extensions( diff --git a/tests/server/test_agent_card.py b/tests/server/test_agent_card.py index cb0e773..506eeb3 100644 --- a/tests/server/test_agent_card.py +++ b/tests/server/test_agent_card.py @@ -41,8 +41,15 @@ def test_agent_card_description_reflects_actual_transport_capabilities() -> None assert card.security == [{"bearerAuth": []}] assert skills_by_id["opencode.chat"].input_modes == ["text/plain", "application/octet-stream"] assert skills_by_id["opencode.chat"].output_modes == ["text/plain", "application/json"] - assert skills_by_id["opencode.sessions.query"].input_modes == ["application/json"] - assert skills_by_id["opencode.sessions.query"].output_modes == ["application/json"] + assert skills_by_id["opencode.sessions.query"].input_modes == [ + "application/json", + "text/plain", + "application/octet-stream", + ] + assert skills_by_id["opencode.sessions.query"].output_modes == [ + "application/json", + "text/plain", + ] assert skills_by_id["opencode.interrupt.callback"].input_modes == ["application/json"] assert skills_by_id["opencode.interrupt.callback"].output_modes == ["application/json"] diff --git a/tests/server/test_transport_contract.py b/tests/server/test_transport_contract.py index 15c5160..3b44135 100644 --- a/tests/server/test_transport_contract.py +++ b/tests/server/test_transport_contract.py @@ -108,7 +108,13 @@ async def test_agent_card_routes_split_public_and_authenticated_extended_contrac assert public_card.headers["cache-control"] == PUBLIC_AGENT_CARD_CACHE_CONTROL assert public_card.headers["etag"] assert public_card.headers["vary"] == "Accept-Encoding" - assert public_card.json()["supportsAuthenticatedExtendedCard"] is True + + card_json = public_card.json() + assert card_json["supportsAuthenticatedExtendedCard"] is True + assert "inputModes" in card_json + assert "outputModes" in card_json + assert card_json["inputModes"] == card_json["defaultInputModes"] + assert card_json["outputModes"] == card_json["defaultOutputModes"] public_cached = await client.get( "/.well-known/agent-card.json",