Skip to content
Merged
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
4 changes: 2 additions & 2 deletions a2a/claude_agent/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ readme = "README.md"
license = { text = "Apache" }
requires-python = ">=3.11"
dependencies = [
# http-server extra pulls in the Starlette/SSE stack that A2AStarletteApplication needs.
"a2a-sdk[http-server]==0.3.26",
# http-server extra pulls in the Starlette/SSE stack the route factories need.
"a2a-sdk[http-server]>=1.1.0,<2",
"pydantic-settings>=2.14.1",
"uvicorn>=0.30",
"starlette>=0.49.1", # Indirect; prevents CVE-2025-62727
Expand Down
31 changes: 21 additions & 10 deletions a2a/claude_agent/src/claude_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
from textwrap import dedent

import uvicorn
from starlette.applications import Starlette

from a2a.helpers import new_task_from_user_message
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.apps import A2AStarletteApplication
from a2a.server.events.event_queue import EventQueue
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
from a2a.server.tasks import InMemoryTaskStore, TaskUpdater
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from a2a.utils import new_task
from a2a.types import AgentCapabilities, AgentCard, AgentInterface, AgentSkill
from claude_agent.configuration import Configuration
from claude_agent.events import StreamTranslator
from claude_agent.runner import run_turn
Expand Down Expand Up @@ -46,7 +47,12 @@ def get_agent_card(host: str, port: int) -> AgentCard:
- **prompt** (string) – the instruction or question for Claude.
"""
),
url=os.getenv("AGENT_ENDPOINT", f"http://{host}:{port}").rstrip("/") + "/",
supported_interfaces=[
AgentInterface(
url=os.getenv("AGENT_ENDPOINT", f"http://{host}:{port}").rstrip("/") + "/",
protocol_binding="JSONRPC",
)
],
version="1.0.0",
default_input_modes=["text"],
default_output_modes=["text"],
Expand All @@ -71,7 +77,7 @@ def __init__(
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
task = context.current_task
if not task:
task = new_task(context.message) # type: ignore
task = new_task_from_user_message(context.message) # type: ignore
await event_queue.enqueue_event(task)
task_updater = TaskUpdater(event_queue, task.id, task.context_id)
translator = StreamTranslator(task_updater)
Expand Down Expand Up @@ -118,11 +124,16 @@ def run() -> None:
request_handler = DefaultRequestHandler(
agent_executor=ClaudeAgentExecutor(config, registry, semaphore),
task_store=InMemoryTaskStore(),
agent_card=agent_card,
)
server = A2AStarletteApplication(agent_card=agent_card, http_handler=request_handler)
# build() serves the agent card at /.well-known/agent-card.json natively
# (a2a-sdk's default AGENT_CARD_WELL_KNOWN_PATH), plus the legacy
# /.well-known/agent.json for back-compat — no custom route needed.
app = server.build()
# a2a-sdk 1.x replaced A2AStarletteApplication with route factories that we
# assemble into a Starlette app ourselves. Serve the current well-known path
# (/.well-known/agent-card.json) plus the legacy /.well-known/agent.json for
# back-compat.
# enable_v0_3_compat is needed because Kagenti uses A2A 0.3 client libraries
routes = create_jsonrpc_routes(request_handler, rpc_url="/", enable_v0_3_compat=True)
routes += create_agent_card_routes(agent_card)
routes += create_agent_card_routes(agent_card, card_url="/.well-known/agent.json")
app = Starlette(routes=routes)

uvicorn.run(app, host=config.host, port=config.port)
12 changes: 6 additions & 6 deletions a2a/claude_agent/src/claude_agent/events.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import json
import logging

from a2a.types import TaskState, TextPart
from a2a.utils import new_agent_text_message
from a2a.helpers import new_text_part
from a2a.types import TaskState

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -33,8 +33,8 @@ async def _working(self, text: str) -> None:
if not text.strip():
return
await self._tu.update_status(
TaskState.working,
new_agent_text_message(text, self._tu.context_id, self._tu.task_id),
TaskState.TASK_STATE_WORKING,
self._tu.new_agent_message([new_text_part(text)]),
)

async def handle(self, event: dict) -> None:
Expand Down Expand Up @@ -70,8 +70,8 @@ async def finish(self) -> None:
"""Emit the terminal A2A state based on what was accumulated."""
if self.errored or self.final_text is None:
reason = self.error_reason or "Claude produced no result"
await self._tu.add_artifact([TextPart(text=f"Error: {reason}")])
await self._tu.add_artifact([new_text_part(f"Error: {reason}")])
await self._tu.failed()
else:
await self._tu.add_artifact([TextPart(text=self.final_text)])
await self._tu.add_artifact([new_text_part(self.final_text)])
await self._tu.complete()
3 changes: 2 additions & 1 deletion a2a/claude_agent/tests/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
def test_agent_card_has_streaming_and_url():
card = get_agent_card("0.0.0.0", 8000)
assert card.capabilities.streaming is True
assert card.url.endswith("/")
# a2a-sdk 1.x moved the URL from AgentCard.url into supported_interfaces.
assert card.supported_interfaces[0].url.endswith("/")
assert card.skills # at least one skill advertised


Expand Down
Loading
Loading