feat(a2a): A2A v1.0 compatibility - ProtoJSON enums, well-known fallback, a2a+json media type - #2515
Merged
Merged
Conversation
…ack, a2a+json media type Aligns ggcode's A2A implementation with the v1.0 spec (2026-03) while keeping 0.2.x/0.3.x peers working: - types.go: TaskState.UnmarshalJSON accepts both legacy lowercase names and v1.0 ProtoJSON enum names (TASK_STATE_*, changelog 1.0.0 #1384 / ADR-001), plus historical spellings (cancelled, input_required). Without this, a v1.0 remote agent reporting TASK_STATE_COMPLETED decoded as an unknown non-terminal state and callers waited forever on finished tasks. MarshalJSON still emits legacy lowercase (1.0.0 #1401 compat allowance); V1Name() provides the proto encoding. AgentCard gains the required "protocolVersion" field. - client.go: Discover probes the v1 canonical /.well-known/agent-card.json first, falling back to legacy /.well-known/agent.json only on 404/405 or an unusable (name+url empty) response - signature/decode errors on a real card surface as-is instead of being masked by a retry. Client switches POST Content-Type to application/a2a+json for peers whose card declares protocolVersion 1.x (1.0.1 #1753 preference), and sync JSON-error detection now recognizes both media types (a plain "application/json" substring check misclassified a2a+json error responses as SSE). - server.go: AgentCard advertises protocolVersion; handleRPC negotiates response media type - requests signaling a2a+json get a2a+json responses, legacy peers keep application/json. rpcWriter forwards Flush so SSE streams survive the wrapper. - v1_compat_test.go: 9 tests covering enum normalization, terminal detection, legacy marshal, media-type selection, well-known fallback, and server negotiation. Verified: go build/vet -tags goolm (darwin + linux/arm64), full internal/a2a test suite green. Co-Authored-By: ggcode <noreply@ggcode.dev> Co-Authored-By: ggcode <noreply@ggcode.dev>
Companion-test follow-up for PR #2515: the outbound server card's protocolVersion serialization had no explicit assertion; v1 clients depend on it for media-type and enum-encoding negotiation. Co-Authored-By: ggcode <noreply@ggcode.dev>
topcheer
commented
Sep 18, 2026
topcheer
left a comment
Owner
Author
There was a problem hiding this comment.
复审通过 ✅(三重点核销)
① ProtoJSON 枚举双读兼容:UnmarshalJSON 归一化(legacy 小写+TASK_STATE_*+历史拼写 cancelled/input_required 全 alias 表)→ canonical 常量——IsTerminal 语义恢复(issue 核心无限等待 bug 闭环);未知态 verbatim 保留(前向兼容);出站维持小写(SDK backwards-compat #1401——旧 peer 零破坏)+V1Name() 显式供给 v1 传输层。8+2 双形态测试矩阵。
② well-known fallback 判定顺序:新路径优先(v1 是演进方向)→仅 404/405(path absent)触发回退旧路径——签名验证失败/decode 错/传输错不回退(真错误浮出不masking);errCardInvalid(200 但无 name/url)也算 invalid 走回退——redirect 落无关 JSON 端点不会静默产空卡。优先级语义=版本探测而非类型冲突。
③ Accept 头交互:acceptHeader 双列(a2a+json, application/json)——与 #2483 扩展协商头正交(不同 header 名零冲突);请求 Content-Type 由对端卡片协议版本门控(card 1.x→a2a+json/legacy→json)——旧严格服务器零影响;响应侧 isJSONMedia 双认(sync error 检测对两种 CT 都工作)。
CI 9/9 绿。可合并。
5 tasks
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.
背景
对标 A2A 规范 v1.0(2026-03)的互操作性缺口。通过 1.0.x changelog 与 ADR-001 研究确认三处协议级不兼容:
TASK_STATE_COMPLETED等枚举名,而 ggcode 的TaskState仅认小写completed。未知状态IsTerminal()==false,调用方会对已完成的任务无限等待。agent-card.json,旧 ggcode 同类只提供agent.json,两侧互发现均失败。application/a2a+json(1.0.1 [code-review] repl_stdout_health hardcodes 0x800 as O_NONBLOCK but on darwin 0x800 is O_EXCL and O_NONBLOCK is 0x4 - F_SETFL silently ignores the open-only bit, the fd stays blocking, and the probe write to a display-slept terminal blocks forever, so the stdout health monitor that exists precisely for the macOS display-sleep hang is a placebo on the primary darwin/arm64 platform (the tty guard in the same directory already uses unix.O_NONBLOCK); on Linux where the bit does apply the probe races the renderer over the shared fd (EAGAIN on in-flight frames, stray SGR resets) #1753):v1 HTTP 绑定首选该媒体类型。服务端从不协商响应类型;客户端的同步错误检测用strings.Contains(ct, "application/json")判断,不包含a2a+json子串 → 把 v1 服务器的 JSON 错误响应当 SSE 喂给解码器,静默空流。改动
internal/a2a/types.goTaskState.UnmarshalJSON双格式归一化(legacy 小写 / v1 ProtoJSON 名 / 历史拼写cancelled、input_required);MarshalJSON保持小写输出(1.0.0 #1401 向后兼容许可);V1Name()提供 proto 编码;AgentCard增加必填的protocolVersion字段,服务端卡片填充A2AProtocolVersioninternal/a2a/client.goDiscover先探 v1 规范路径,仅在 404/405 或非卡片响应(name/url 全空)时回退 legacy 路径——真实卡片的签名/解码错误原样上报,不被重试掩盖;卡片声明 1.x 协议版本时 POST 体切换a2a+json;Accept 同时声明两种媒体类型;同步错误检测双媒体识别internal/a2a/server.goprotocolVersion;handleRPC按请求信号协商响应媒体类型(a2a+json → a2a+json,legacy → application/json);rpcWriter透传Flush保 SSEinternal/a2a/v1_compat_test.godocs/guide/a2a.md验证
go build -tags goolm ./...全项目通过go vet -tags goolmdarwin + linux/arm64 双平台通过internal/a2a全量测试绿(含既有 e2e、签名防篡改、SSE 流、redirect 凭证剥离等回归)Co-Authored-By: ggcode noreply@ggcode.dev