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 workspace/a2a_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async def delegate(target_id: str, task: str, async_mode: bool = False):
"method": "message/send",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"messageId": str(uuid.uuid4()),
"parts": [{"kind": "text", "text": task}],
}
Expand Down Expand Up @@ -108,7 +108,7 @@ async def delegate(target_id: str, task: str, async_mode: bool = False):
"method": "message/send",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"messageId": str(uuid.uuid4()),
"parts": [{"kind": "text", "text": task}],
}
Expand Down
2 changes: 1 addition & 1 deletion workspace/a2a_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async def send_a2a_message(target_url: str, message: str) -> str:
"method": "message/send",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"messageId": str(uuid.uuid4()),
"parts": [{"kind": "text", "text": message}],
}
Expand Down
34 changes: 23 additions & 11 deletions workspace/a2a_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,20 +446,32 @@ async def _core_execute(self, context: RequestContext, event_queue: EventQueue)
# making the earlier `Part(text=text)` call (line ~358, inside
# the astream_events loop) raise UnboundLocalError because
# the local binding is not yet in scope at that point.
from a2a.types import FilePart, FileWithUri, Message, Role, TextPart
_parts: list[Part] = [Part(root=TextPart(text=final_text))] if final_text else []
#
# a2a-sdk 1.x flattened the Part shape: 0.x used
# `Part(root=TextPart(text=...))` / `Part(root=FilePart(file=
# FileWithUri(uri=..., name=..., mimeType=...)))` (Pydantic
# discriminated-union style). 1.x's Part is a single proto
# message with flat fields: text, url, filename, media_type,
# raw, data, metadata. TextPart/FilePart/FileWithUri were
# removed. Same for Message: messageId/taskId/contextId
# camelCase became message_id/task_id/context_id.
from a2a.types import Message, Role
_parts: list[Part] = [Part(text=final_text)] if final_text else []
for f in _outbound:
_parts.append(Part(root=FilePart(file=FileWithUri(
uri="workspace:" + f["path"],
name=f["name"],
mimeType=f["mime_type"],
))))
_parts.append(Part(
url="workspace:" + f["path"],
filename=f["name"],
media_type=f["mime_type"],
))
msg = Message(
messageId=uuid.uuid4().hex,
role=Role.agent,
message_id=uuid.uuid4().hex,
# 1.x Role is a protobuf enum: ROLE_UNSPECIFIED,
# ROLE_USER, ROLE_AGENT. Old `Role.agent` (Pydantic
# lowercase enum) doesn't exist anymore.
role=Role.ROLE_AGENT,
parts=_parts,
taskId=task_id,
contextId=context_id,
task_id=task_id,
context_id=context_id,
)
else:
msg = new_text_message(final_text, task_id=task_id, context_id=context_id)
Expand Down
8 changes: 4 additions & 4 deletions workspace/adapters/google-adk/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.helpers import new_agent_text_message
from a2a.helpers import new_text_message

from adapter_base import AdapterConfig, BaseAdapter

Expand Down Expand Up @@ -191,7 +191,7 @@ async def execute(self, context: RequestContext, event_queue: EventQueue) -> Non
if not user_text:
parts = getattr(getattr(context, "message", None), "parts", None)
logger.warning("GoogleADKA2AExecutor: no text in message parts: %s", parts)
await event_queue.enqueue_event(new_agent_text_message(_NO_TEXT_MSG))
await event_queue.enqueue_event(new_text_message(_NO_TEXT_MSG))
return

session_id = getattr(context, "context_id", None) or "default-session"
Expand Down Expand Up @@ -223,7 +223,7 @@ async def execute(self, context: RequestContext, event_queue: EventQueue) -> Non
response_parts.append(text)

final_text = "".join(response_parts).strip() or _NO_RESPONSE_MSG
await event_queue.enqueue_event(new_agent_text_message(final_text))
await event_queue.enqueue_event(new_text_message(final_text))

except Exception as exc:
logger.error(
Expand All @@ -234,7 +234,7 @@ async def execute(self, context: RequestContext, event_queue: EventQueue) -> Non
)
# Mirror sanitize_agent_error() convention: expose class name only.
await event_queue.enqueue_event(
new_agent_text_message(f"Agent error: {type(exc).__name__}")
new_text_message(f"Agent error: {type(exc).__name__}")
)

async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
Expand Down
2 changes: 1 addition & 1 deletion workspace/builtin_tools/a2a_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async def delegate_task(workspace_id: str, task: str) -> str:
"method": "message/send",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"messageId": str(uuid.uuid4()),
"parts": [{"kind": "text", "text": task}],
},
Expand Down
2 changes: 1 addition & 1 deletion workspace/builtin_tools/delegation.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ async def _execute_delegation(task_id: str, workspace_id: str, task: str):
"id": f"delegation-{task_id}-{attempt}",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"parts": [{"kind": "text", "text": task}],
"messageId": f"msg-{task_id}-{attempt}",
},
Expand Down
2 changes: 1 addition & 1 deletion workspace/heartbeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ async def _check_delegations(self, client: httpx.AsyncClient):
"method": "message/send",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"parts": [{"type": "text", "text": trigger_msg}],
},
},
Expand Down
18 changes: 14 additions & 4 deletions workspace/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,20 @@ async def main(): # pragma: no cover
agent_card=agent_card,
)

# v1: replace A2AStarletteApplication with Starlette route factory
# v1: replace A2AStarletteApplication with Starlette route factory.
# rpc_url is required in a2a-sdk 1.x (was implicit at root in 0.x).
# Use '/' to match a2a.utils.constants.DEFAULT_RPC_URL — that's also
# what the platform's a2a_proxy.go POSTs to (it forwards to the
# workspace's URL without appending a path). Card endpoint stays at
# the well-known path /.well-known/agent-card.json (handled by
# create_agent_card_routes default).
routes = []
routes.extend(create_agent_card_routes(agent_card))
routes.extend(create_jsonrpc_routes(request_handler=handler))
# enable_v0_3_compat=True so any external 0.3.x A2A client (still using
# `"role": "user"` lowercase + camelCase Pydantic field names) can talk
# to us without re-deploying. Internally our outbound payloads are now
# 1.x-shaped (ROLE_USER), but inbound is opt-in compatible.
routes.extend(create_jsonrpc_routes(request_handler=handler, rpc_url="/", enable_v0_3_compat=True))
app = Starlette(routes=routes)

# 8. Register with platform
Expand Down Expand Up @@ -451,7 +461,7 @@ def _do_send_sync():
"method": "message/send",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"messageId": f"initial-{_uuid.uuid4().hex[:8]}",
"parts": [{"kind": "text", "text": config.initial_prompt}],
},
Expand Down Expand Up @@ -550,7 +560,7 @@ async def _run_idle_loop():
"method": "message/send",
"params": {
"message": {
"role": "user",
"role": "ROLE_USER",
"messageId": f"idle-{_uuid.uuid4().hex[:8]}",
"parts": [{"kind": "text", "text": config.idle_prompt}],
},
Expand Down
Loading