fix(a2a-v1): rewrite FilePart emit using v1 protobuf Part struct
a2a-sdk v1.0.2 replaced the v0 Pydantic discriminated-union types
(Part(root=TextPart(...))/Part(root=FilePart(file=FileWithUri(...))))
with a single protobuf Part struct that has optional `text`, `url`,
`raw`, `data`, `filename`, `media_type` fields. The classes
FilePart, TextPart, FileWithUri don't exist in v1 — import fails:
File "claude_sdk_executor.py", line 592
from a2a.types import FilePart, FileWithUri, Message, Part, Role, TextPart
ImportError: cannot import name 'FilePart' from 'a2a.types'
Production impact: every claude-code workspace (Design Director, UX
Researcher, all coordinators in molecule-core teams) crashes on
result delivery whenever the response includes a /workspace/* file
reference. The A2A delegation loop is broken at the result-delivery
step. Workspaces can receive tasks but can't ship results back.
Fix:
- Drop FilePart/TextPart/FileWithUri imports (don't exist in v1).
- `Part(root=TextPart(text=t))` → `Part(text=t)`.
- `Part(root=FilePart(file=FileWithUri(uri=u, name=n, mimeType=m)))` →
`Part(url=u, filename=n, media_type=m)`.
- `messageId=...` → `message_id=...` (snake_case in protobuf).
- `Role.agent` → `Role.ROLE_AGENT` (v1 enum).
Verified by constructing the exact shape against v1.0.2 in the
running claude-code template image:
Message:
message_id: 03ff9367
role: ROLE_AGENT
parts count: 2
text part: hello
file part: workspace:foo.txt foo.txt text/plain
Refs: molecule-core memory `reference_a2a_sdk_v0_to_v1_migration`
documents the Pydantic→protobuf shift; this is the fifth migration
finding today (after the new_agent_text_message rename in
crewai/openclaw/autogen/gemini-cli).
Test plan:
- [x] `python3 -m py_compile claude_sdk_executor.py` clean.
- [x] Runtime construction smoke verified against the live v1.0.2
a2a-sdk in the claude-code template image.
- [ ] End-to-end: provision a claude-code workspace, send a task
whose response references a /workspace/* file, confirm
result lands without ImportError.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
de2ab5ab33
commit
1a84de8a61
@ -586,21 +586,27 @@ class ClaudeSDKExecutor(AgentExecutor):
|
||||
# preparing its prompt while this turn's response ships. Event
|
||||
# ordering is preserved per-queue by the A2A server, so no races.
|
||||
# If the response mentions /workspace/... files, stage each and
|
||||
# emit FileParts alongside the text so the canvas can download.
|
||||
# emit file parts alongside the text so the canvas can download.
|
||||
#
|
||||
# a2a-sdk v1 uses protobuf, NOT the v0 Pydantic discriminated-union
|
||||
# types. There is no FilePart / TextPart / FileWithUri class — Part
|
||||
# is one struct with optional `text`, `url`, `raw`, `data`,
|
||||
# `filename`, `media_type` fields (plus `metadata`). Set the field
|
||||
# that matches the part's nature; leave the rest unset.
|
||||
outbound = collect_outbound_files(response_text)
|
||||
if outbound:
|
||||
from a2a.types import FilePart, FileWithUri, Message, Part, Role, TextPart
|
||||
from a2a.types import Message, Part, Role
|
||||
import uuid as _uuid
|
||||
parts: list = [Part(root=TextPart(text=response_text))] if response_text else []
|
||||
parts: list = [Part(text=response_text)] if response_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"],
|
||||
))
|
||||
await event_queue.enqueue_event(Message(
|
||||
messageId=_uuid.uuid4().hex,
|
||||
role=Role.agent,
|
||||
message_id=_uuid.uuid4().hex,
|
||||
role=Role.ROLE_AGENT,
|
||||
parts=parts,
|
||||
))
|
||||
else:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user