Skip to content

refactor: finalize a2a v1-only migration cleanup#424

Merged
liujuanjuan1984 merged 14 commits intomainfrom
chore/423-a2a-v1-migration-gap-audit
Apr 28, 2026
Merged

refactor: finalize a2a v1-only migration cleanup#424
liujuanjuan1984 merged 14 commits intomainfrom
chore/423-a2a-v1-migration-gap-audit

Conversation

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator

@liujuanjuan1984 liujuanjuan1984 commented Apr 27, 2026

背景

  • 对照 ~/a2a-python/docs/migrations/v1_0/README.md 的迁移点,完成仓库内 A2A v1 升级的最终收口。
  • 当前运行时明确采用 v1-only 策略,不再兼容旧版 0.3 协议语义。
  • 本 PR 同时收敛迁移过程中遗留的薄套壳、冗余配置与非必要兼容,尽量回到 A2A SDK v1 的直接 protobuf 用法。

协议与配置

  • 固定仓库运行时仅支持 1.0 协议线,移除旧协议方法别名、旧 payload 识别和“可配置但只能是 1.0”的噪音通路。
  • Agent Card、OpenAPI、compatibility profile、health payload 与公共文档统一更新为 v1-only 口径。
  • 客户端默认头固定发送 A2A-Version: 1.0,删除 A2A_PROTOCOL_VERSION / A2A_CLIENT_PROTOCOL_VERSION 等冗余回退读取。

客户端与出站链路

  • A2AClient.send_message() / subscribe_to_task() 直接返回原生 StreamResponse,不再适配仓库私有 tuple 事件。
  • 客户端请求上下文、payload text、tool orchestration、stream runtime 全链路直接消费 SDK v1 protobuf 能力。
  • 保留有明确业务价值的 polling fallback 与结构化输出降级,不再为旧协议或历史 facade 保留兼容壳层。

服务端、Transport 与 JSON-RPC

  • REST / JSON-RPC 主路径统一到 v1 canonical 语义,拒绝 legacy message.content、小写 role、{kind: ...} 包装和旧 method alias。
  • 删除 /.well-known/agent.jsonon_resubscribe_to_task()、旧 REST error flat payload、旧 push-notification 兼容包装等残留。
  • GetTask / SubscribeToTask / list tasks / output negotiation 等读取路径同步保持 v1 输出与扩展裁剪语义。

扩展协商与 protobuf 收敛

  • extension negotiation 改为围绕真实消费点直接实现,删除单调用薄封装与 metadata 包装函数。
  • 仓内消费尽量直接走 protobuf 字段访问;只在 JSON-RPC / REST /持久化等真实边界执行必要的 MessageToDict
  • opencode.sessions.messages.* 明确保留为 adapter-normalized message projection,并在文档中显式说明边界。

文档与测试

  • README、guide、compatibility、conformance、extension specs 等文档已同步到 v1-only 边界。
  • 针对协议版本、扩展协商、output negotiation、transport contract、client facade 等路径补齐并更新测试。
  • 构建 warning vcs_versioning GlobalOverrides 继续按上游 packaging warning 跟踪,不在本 PR 内引入额外 build backend 定制。

死代码审查

  • 复核了全仓生产代码与测试支撑代码的引用链、覆盖率与当前 PR 改动面。
  • 未再发现可高置信删除的生产死代码;当前剩余低引用 helper 要么是公开 API,要么是 pytest/autouse、protobuf 边界、SQLAlchemy 事件钩子或契约校验逻辑,不做盲删。
  • 本 PR 中已经继续删除了确认无收益的兼容壳层、单调用 helper 和历史薄包装。

范围外 follow-up

验证

  • bash ./scripts/doctor.sh
  • 596 passed
  • coverage 91.43%
  • 包含 lint、mypy、全量测试、sdist/wheel 构建与 smoke test

Issues

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

补充说明:构建阶段 vcs_versioningGlobalOverrides warning,这次刻意不在主仓库里引入自定义 build backend 来消音。当前判断是把它视为上游 packaging warning 跟踪更合适,因为它不影响版本推断、sdist/wheel 构建结果和 smoke test;为此新增 backend 会增加 packaging 维护面,当前收益不足。

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

独立复审结论(对照 ~/a2a-python/docs/migrations/v1_0/README.md:64:500):

  • 已确认主线迁移点完成:TaskState / Role 已统一使用 SCREAMING_SNAKE_CASEPart 已统一使用 text/raw/url/data 直字段,create_client()StreamResponse.HasField() 语义已在生产代码落地。
  • 已再次扫过 src/ / tests/ / docs/,没有再发现 TextPart / FilePart / DataPart / ClientFactory / message/send / tasks/resubscribe / 0.3 协议别名这类旧协议兼容残留。
  • 当前没有新的高置信迁移缺口;也没有发现还需要立即删除的旧版协议兼容壳。

剩余更像仓库自有抽象,而不是 v0.3 兼容残留:

  • src/opencode_a2a/a2a_utils.py 仍维护了一层仓库自有 protobuf helper(如 make_text_part() / make_data_part() / part_text_fallback()),功能上已是 v1 语义,但尚未收敛到官方 a2a.helpers 统一入口。
  • src/opencode_a2a/client/client.py 仍保留仓库级 facade 能力,例如 send() 的 polling fallback 与 auth/request-metadata 包装;这不是旧协议兼容,而是本仓库的 client DX 选择。
  • src/opencode_a2a/server/state_store.py 等处的 legacy_* 命名,针对的是内部状态表/持久化数据迁移,不是 A2A wire protocol 兼容。

结论:A2A v1 迁移主线已完成;当前剩余的是仓库自有 helper / facade / 内部数据迁移命名,不应再归类为协议迁移缺口。

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

补充两点审查结论:

  1. a2a_utils 当前不是一个纯“旧协议兼容层”,而是混合了三类职责:

    • protobuf 复制/替换/比较工具
    • Part 构造 helper
    • 输出协商时的结构化 -> 文本降级 helper
  2. 从消费面看,当前最值得继续收敛的不是整包删除,而是把“简单构造”与“必要工具”分开:

    • 保留价值较高:clone_proto / proto_to_dict / proto_equals / replace_message_parts / replace_artifact_parts / replace_status_event_message / replace_artifact_event_artifact,这类是 protobuf 变换工具,官方 a2a.helpers 不覆盖。
    • 业务上合理:part_text_fallback()make_data_part(),前者服务于 output negotiation 的 data -> text/plain 降级,后者服务于流式 structured payload 构造。
    • 明显偏薄壳:make_text_part() 当前在生产代码有 12 处调用,大多只是 Part(text=...) 的一层包裹;这类写法不比直接 protobuf 构造更清晰,也不比官方迁移建议更贴近 v1。
    • 高置信低价值残留:make_raw_part() / make_url_part() 当前无生产代码消费,只在测试中使用;replace_task_status_message() / replace_task_history() / replace_task_artifacts() / replace_task_metadata() 当前没有仓库消费。

另外,内部状态表旧行 TTL fallback 已拆出单独问题跟踪:#425。它与 A2A 协议升级解耦,不在当前 PR 内继续展开。

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

继续收敛完成,已提交并推送:b3b69c8 (refactor: remove low-value a2a part helpers)

这轮聚焦处理了上一轮审查里“最值得继续收敛”和“高置信低价值残留”的两类 helper:

  • 生产代码不再通过 make_text_part() 这类薄壳构造简单文本 part,client / server / execution / jsonrpc / output_modes 主路径已直接改为 Part(text=...)
  • 删除 src/opencode_a2a/a2a_utils.py 中无生产价值的 helper:
    • 已删除:make_text_part()make_raw_part()make_url_part()
    • 已删除:part_is_text()part_is_data()part_is_file()
    • 已删除:part_data_to_python()
    • 已删除:replace_task_status_message()replace_task_history()replace_task_artifacts()replace_task_metadata()
  • 保留了仍有明确价值的 helper:protobuf clone/compare/dict 转换、make_data_part()part_text_fallback()、以及 output negotiation 所需的 artifact/status 替换工具。
  • 测试同步改为直接构造 Part(...) 或直接读取 protobuf data,不再要求生产模块继续为测试保留低价值 helper。

验证结果:

  • ./scripts/doctor.sh passed
  • 591 passed
  • total coverage 91.37%
  • 构建阶段仍会出现已接受的 vcs_versioning GlobalOverrides warning;处理策略不变,继续按上游 packaging warning 跟踪。

@liujuanjuan1984 liujuanjuan1984 changed the title refactor: remove remaining a2a v1 compat shims refactor: finish a2a v1-only migration cleanup Apr 27, 2026
@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

独立复审结果:未发现需要阻塞合并的问题。

本次复审范围包括:PR diff、相关 commits(eb2d593 / 71a33c5 / b3b69c8)、文档口径、以及 ./scripts/doctor.sh 回归结果。

结论:

  • 改动方向与 #423 想要解决的问题一致,主线目标明确:完成 A2A v1-only 收口,移除不必要的旧兼容与低价值 helper 包装。
  • 实现方式整体是合理的:协议面、客户端、服务端、JSON-RPC、CLI、测试与文档同步收口,没有出现“只改运行时、不改契约/文档”的偏差。
  • 最近一轮 helper 清理是加分项:把简单文本 part 构造改回直接 Part(text=...),比继续维持仓库私有薄壳更贴近 A2A SDK v1 的推荐用法。

关于优雅性与稳健性:

  • 当前保留的 a2a_utils 已基本收窄到有明确价值的 protobuf 变换/比较工具,以及 output negotiation 需要的 make_data_part() / part_text_fallback();这部分仍有清晰消费,不属于冗余兼容层。
  • state_store 的旧行 TTL fallback 没有继续混进本 PR,是合理的边界控制;单独拆到 #425 跟进,比继续把内部状态表历史包袱夹带进协议升级 PR 更稳妥。

残余非阻塞关注点:

  • 这是明确的 breaking change,后续正式发布时应按 minor 线处理,并在 release note 里明确声明不再兼容 0.3
  • vcs_versioningGlobalOverrides warning 仍然存在,但 PR 描述已准确说明当前策略;在不引入自定义 build backend 的前提下,维持现状是合理取舍。

PR / issue 关系复核:

  • Closes #423:准确。
  • Related #425:准确;#425 是内部 state store 清理 follow-up,不应在本 PR 中标记为 Closes

总体判断:当前 PR 的代码与文档变动已经较为完整、边界清晰、回归充分,可以进入正常审查/合并流程。

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

独立审查结论:对照 a2a-python 1.0+ 的扩展实践,当前实现属于“能力声明基本符合、标准方法承载部分符合、协商头实现存在明显缺口”。

  1. AgentCard.capabilities.extensions:符合。

    • 当前 Agent Card 已通过 AgentCapabilities.extensions 声明共享扩展与 OpenCode provider-private 扩展,方向与 SDK v1 一致。
  2. A2A-Extensions 协商:服务端基本符合,客户端不符合。

    • 服务端 REST / JSON-RPC 都接入了 DefaultServerCallContextBuilder,因此能读取 A2A-Extensions 并写入 requested_extensions
    • 但仓库自带 A2AClient 目前只把扩展 URI 放进 Message.extensions,没有把扩展 URI 写入 ClientCallContext.service_parameters,因此不会按 SDK 推荐方式发出 A2A-Extensions header。
    • 这意味着“声明了扩展”与“实际通过 header 协商扩展”之间还有断层。
  3. 用标准 A2A 方法承载扩展行为:部分符合。

    • metadata.shared.session.idmetadata.shared.model.*、流式元数据/中断/usage 等共享扩展,已经围绕 SendMessage / SendStreamingMessage / GetTask 等标准 A2A 主路径承载,这部分方向正确。
    • 但 provider-private 能力仍大量通过 opencode.sessions.*opencode.providers.*opencode.workspaces.*a2a.interrupt.* 自定义 JSON-RPC 方法暴露。
    • 这些方法并非错误;如果目标是“OpenCode 私有控制面”,当前建模是可辩护的;但如果目标是尽量贴近官方“标准方法 + 扩展协商”实践,那么这部分仍明显更偏产品私有 API,而不是共享 A2A 扩展语义。

建议:如果后续继续收敛,优先级最高的是把客户端扩展 URI 通过 A2A-Extensions header 显式发出;其次再评估哪些 provider-private 行为真的必须保留为自定义方法,哪些可以继续下沉到标准 A2A message/task 流程。

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

本轮基于 #426 已完成 extensions 官方最佳实践收敛,提交 8e8b07d 已推送到当前分支。

改动重点:

  • 能力声明继续以 AgentCard.capabilities.extensions 为单一来源。
  • 协商路径改为显式使用 A2A-Extensions,client 通过 service_parameters 发送,server / JSON-RPC / REST 统一读取并校验。
  • 标准 SendMessage / GetTask / SubscribeToTask / ListTasks 和流式执行链路现在都会基于协商结果决定是否接受或返回 metadata.shared.*
  • 扩展 JSON-RPC 方法在未协商时会返回稳定的 EXTENSION_NEGOTIATION_REQUIRED,不再隐式放行。
  • 去掉了 client 里把扩展 URI 冗余放进 Message.extensions 的做法,避免 header 协商与消息体双轨语义。
  • 测试与契约基座已同步到显式协商模型。

回归结果:

  • ./scripts/doctor.sh passed
  • 599 passed
  • coverage 91.31%

关系说明:

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

补充同步本轮收口结果:\n\n- 已继续处理并完成 #427 中审查出的剩余迁移缺口与非必要兼容。\n- 新增提交:67f4b47 Align A2A v1 compatibility surfaces (#427)\n- 新增提交:e7b60aa Format extension negotiation helpers (#427)\n- opencode.sessions.messages.* 已统一输出 v1 canonical ROLE_*。\n- GET /v1/tasks 不再接受旧式 status=completed。\n- /.well-known/agent.json 已从服务端公开发现面移除,客户端对 legacy URL 会明确报错。\n- ./scripts/doctor.sh 已再次完整通过。\n\n#427 已关闭。保留行为仅剩已文档化的 opencode.sessions.messages.* adapter-normalized message projection。

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

补充一轮 v1-only 清理:

  • 删除客户端对 A2A_PROTOCOL_VERSION / a2a_protocol_version 的协议版本回退,只保留 A2A_CLIENT_PROTOCOL_VERSION
  • 删除 /.well-known/agent.json 的客户端显式防御分支,避免继续保留 legacy 特判代码。
  • 已同步清理相关测试。

验证已通过:bash ./scripts/doctor.sh

新增提交:5139b1d Refine client v1-only config paths (#428)

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

补充一轮人工审查收口,已随 bd715c1 推送:

  • 将 A2A SDK 兼容线文档表述改为 1.x.y / v1 line,保留 pyproject.toml 对具体 SDK release 的固定。
  • 删除客户端协议版本配置入口与相关噪音,客户端统一发送固定 A2A-Version: 1.0
  • 服务端协议版本通路改为常量化,不再保留“可配置但永远只能是 1.0”的设置、校验和回退。
  • split_request_metadata() 收紧为显式字符串头处理;A2A-Version 不再允许通过 metadata 覆盖,A2A-Extensions 只接受字符串。
  • 删除 extension_negotiation_required_error() 这类单点套壳,以及多处不必要的模块级 __all__ / 间接 re-export。
  • 测试改为直接从真实定义模块导入,不再依赖聚合壳模块导出。

验证:bash ./scripts/doctor.sh 已完整通过。

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

补充同步 #430 的收敛结果,提交为 7589664 Prune proto wrapper noise (#430)

本轮重点:

  • 删掉 proto_to_dict / part_kind / part_text / part_text_fallback 这类极薄壳。
  • 保留必须的 proto -> dict 边界序列化,仅用于 JSON-RPC、REST、持久化和 ETag 等真实消费端。
  • 清理运行时代码里的 legacy_* 命名噪音;legacy_payload 之前已删,这轮又把 legacy_ttl_seconds 改为 fallback_ttl_seconds
  • README / compatibility 文案改为 Supported A2A protocol line: 1.0

验证:

  • bash ./scripts/doctor.sh

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

补充一笔极小的命名收口,提交为 2afe8f9 Clarify pending claim ttl naming (#430)

只改了一处参数名:

  • fallback_ttl_seconds -> ttl_seconds

原因:

  • fallback 语义已经由 _pending_claim_expires_at() 这个函数本身表达,参数名再带前缀只会增加噪音。
  • 行为未变,仍然是在行缺少 expires_at 时,用 updated_at + ttl_seconds 推导过期时间。

验证:

  • bash ./scripts/doctor.sh

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

继续收敛了固定 v1-only 路径里的仓内噪音,已推送提交:bd008bb Prune fixed-v1 plumbing noise (#430)

这次处理了 4 类点:

  • parts/mapping.py 删除薄套壳 helper,仓内直接按 proto Part 消费。
  • 客户端请求上下文删除可选 protocol_version 通路,默认头固定 A2A-Version: 1.0
  • JSON-RPC 内部移除无实际作用的 protocol_version 透传和状态复制。
  • Agent Card / OpenAPI / health 改为直接使用 v1 常量,不再通过 Settings 挂伪配置字段。

验证已完成:bash ./scripts/doctor.sh,结果 596 passed,coverage 91.40%

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

继续收敛了仓内转换噪音,已推送提交:fb8a7c9 Prune extension negotiation conversion noise (#431)

本轮处理:

  • proto_equals() 删除 proto -> dict 比较,直接使用 proto 内容相等比较。
  • stream_runtime 删除 Mapping -> Part.data -> text 的往返转换,按协商结果直接产出最终 part。
  • extension_negotiation.py 删除多层纯套壳 helper,并把 header / metadata 判定链路收成更直接的实现。
  • executor / JSON-RPC 调用点同步精简。

独立审查后保留的部分只有真实边界逻辑:

  • metadata 归一化:Struct -> Python mapping
  • negotiated metadata 裁剪:_set_filtered_metadata() / _filter_metadata_dict()
  • 业务语义承载:ExtensionRequirement

验证:bash ./scripts/doctor.sh,结果 596 passed,coverage 91.46%

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

按“<=3 处调用且函数体 <10 行就拆掉封装”的规则,又收了一轮,已推送提交:ad53226 Inline small extension negotiation helpers (#431)

这次删除了:

  • merge_extension_service_parameters()
  • requested_extensions_from_headers()
  • _normalize_metadata()
  • filter_negotiated_extensions_from_payload() 内部的 _filter_task() / _filter_status_update() / _filter_artifact_update()

当前保留的 helper 都已重新核过:

  • 超过 3 处调用的保留
  • 内部逻辑超过 10 行的保留
  • 不再保留“调用点少、逻辑又薄”的套壳

验证:bash ./scripts/doctor.sh,结果 596 passed,coverage 91.44%

@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

继续按人工审查规则收口了 2 个仅 1 处调用的封装函数:

  • required_extensions_for_send_message_params() 已内联到 server/application.py
  • _filter_metadata_dict() 已内联到 _set_filtered_metadata()

这轮目标是删除无收益的中转封装,不改变对外协议语义。验证已完成:

  • uv run pytest --no-cov tests/server/test_app_behaviors.py tests/server/test_transport_contract.py tests/jsonrpc/test_dispatch_registry.py tests/server/test_output_negotiation.py -q
  • bash ./scripts/doctor.sh

提交:1b0e6ab Inline single-use extension helpers (#431)

@liujuanjuan1984 liujuanjuan1984 changed the title refactor: finish a2a v1-only migration cleanup refactor: finalize a2a v1-only migration cleanup Apr 28, 2026
@liujuanjuan1984
Copy link
Copy Markdown
Collaborator Author

最终收尾审查已完成。

结论:

  • 本 PR 引入的协议、客户端、服务端、扩展协商、文档与测试收口整体方向是合理的,和 a2a-sdk 1.0.x / v1-only 目标一致。
  • 继续做了全仓死代码复核,没有再发现可高置信删除的生产死代码;剩余低引用 helper 主要属于公开 API、pytest/autouse、protobuf 边界、SQLAlchemy 事件钩子或契约校验逻辑,不做盲删。
  • PR 标题与描述已按当前最终代码状态更新,并修正了 issue 关联表述:Closes #423Related #425#427#431 保留为本分支内已单独关闭的 follow-up。

@liujuanjuan1984 liujuanjuan1984 merged commit 424c130 into main Apr 28, 2026
3 checks passed
@liujuanjuan1984 liujuanjuan1984 deleted the chore/423-a2a-v1-migration-gap-audit branch April 28, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant