Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/opencode_a2a/server/agent_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
SecurityScheme,
TransportProtocol,
)
from pydantic import Field

from ..config import Settings
from ..contracts.extensions import (
Expand Down Expand Up @@ -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],
*,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down
11 changes: 9 additions & 2 deletions tests/server/test_agent_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
8 changes: 7 additions & 1 deletion tests/server/test_transport_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down