fix(a2a): send v1 file Part shape; tolerate v1 server-side - #2509
Merged
Merged
Conversation
Image-only chats surface "Error: message contained no text content"
because canvas posts v0 `{kind:"file", file:{uri,name,mimeType}}` shapes
that the workspace runtime's a2a-sdk v1 protobuf parser silently drops:
v1 `Part` has fields `[text, raw, url, data, metadata, filename,
media_type]` and `ignore_unknown_fields=True` discards `kind`+`file`,
producing a fully-empty Part. With no text and no extracted file
attachments, the executor's "no text content" guard fires.
Three coordinated changes close the gap:
1. canvas/ChatTab.tsx — outbound file parts now carry the v1 flat
shape `{url, filename, mediaType}` so the v1 protobuf parser
populates Part fields instead of dropping them.
2. workspace/executor_helpers.py — extract_attached_files learns the
v1 detection branch (non-empty `part.url` + `filename` +
`media_type`) alongside the existing v0 RootModel and flat-file
shapes. Defends every runtime that mounts the OSS wheel against
the same drop, including any pre-fix client still on the wire.
3. canvas/message-parser.ts — extractFilesFromTask tolerates the v1
shape on incoming agent responses too, so file chips render in
chat history regardless of which Part shape the runtime emits.
Test pins:
- workspace/tests/test_executor_helpers.py:
+ v1 protobuf shape extraction
+ empty-Part defense (v0→v1 silent-drop fall-through returns [])
- canvas message-parser test:
+ v1 protobuf flat parts
+ filename fallback to URL basename for v1
HongmingWang-Rabbit
requested a review
from hongmingwang-moleculeai
as a code owner
May 2, 2026 07:58
This was referenced May 2, 2026
HongmingWang-Rabbit
pushed a commit
that referenced
this pull request
Jun 12, 2026
…oard access (core#2509) Fixes #2509 1. switchOrgUrl now accepts `host` (includes :port) instead of `hostname`, so non-443 deployments navigate correctly. (core#2509-1) 2. The /cp/orgs fetch is moved outside the setState updater, eliminating the StrictMode double-fetch. A transient error now resets `orgs` to null when the menu re-opens, so "No other organizations" no longer caches forever. (core#2509-2) 3. The derived apex must contain at least one dot, preventing a 2-label host with empty currentSlug from yielding a foreign apex. (core#2509-3) 4. The org switcher block now has an onKeyDown handler (Enter/Space) so keyboard users can open it. (core#2509-4) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Image-only chats surface
Error: message contained no text content.because canvas posts the v0 file-part shape{kind:"file", file:{uri,name,mimeType}}that the workspace runtime's a2a-sdk v1 protobuf parser silently drops. v1Parthas fields[text, raw, url, data, metadata, filename, media_type]— there is nokind, no nestedfile. Withignore_unknown_fields=True,ParseDictdrops both legacy keys, producing a fully-empty Part. No text, no extracted attachments → the executor's"no text content"guard fires.Three coordinated changes:
{url, filename, mediaType}so the v1 parser populates Part fields instead of dropping them.extract_attached_fileslearns a v1 detection branch (non-emptypart.url+filename+media_type) alongside the existing v0 RootModel and flat-file shapes. Defends every runtime mounting the OSS wheel against any client still on the v0 wire.extractFilesFromTasktolerates the v1 shape on incoming agent responses, so file chips render in chat history regardless of which Part shape the runtime emits.Repro:
```python
from a2a.types import a2a_pb2
from google.protobuf.json_format import ParseDict
v0 = {"kind":"file","file":{"uri":"workspace:/x.png","name":"x.png","mimeType":"image/png"}}
p = ParseDict(v0, a2a_pb2.Part(), ignore_unknown_fields=True)
All fields empty → silently dropped by extract_attached_files (pre-fix).
```
Test plan
86 passed)91 passed)"no text content"errorSurfaced 2026-05-01 in hongming workspace after the
/workspacechown fix unblocked uploads.