agent-client-protocol 0.12.0__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/PKG-INFO +10 -7
  2. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/README.md +4 -4
  3. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/agent.py +2 -4
  4. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/client.py +1 -2
  5. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/echo_agent.py +3 -4
  6. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/gemini.py +1 -2
  7. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/http_client.py +4 -3
  8. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/http_server.py +4 -6
  9. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/ws_client.py +6 -3
  10. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/pyproject.toml +15 -5
  11. agent_client_protocol-1.0.0/scripts/_schema_semantics.py +57 -0
  12. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/scripts/gen_all.py +80 -39
  13. agent_client_protocol-1.0.0/scripts/gen_meta.py +96 -0
  14. agent_client_protocol-1.0.0/scripts/gen_schema.py +207 -0
  15. agent_client_protocol-1.0.0/scripts/gen_schema_v1.py +235 -0
  16. agent_client_protocol-1.0.0/scripts/gen_schema_v2.py +235 -0
  17. agent_client_protocol-1.0.0/scripts/gen_signature.py +266 -0
  18. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/__init__.py +58 -49
  19. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_cookies.py +1 -1
  20. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_deserialize.py +20 -1
  21. agent_client_protocol-1.0.0/src/acp/_jsonrpc.py +28 -0
  22. agent_client_protocol-1.0.0/src/acp/_protocol_adapters.py +72 -0
  23. agent_client_protocol-1.0.0/src/acp/_schema_base.py +79 -0
  24. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_sse.py +22 -8
  25. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_transport.py +18 -24
  26. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/agent/connection.py +94 -7
  27. agent_client_protocol-1.0.0/src/acp/agent/router.py +10 -0
  28. agent_client_protocol-1.0.0/src/acp/client/connection.py +626 -0
  29. agent_client_protocol-1.0.0/src/acp/client/router.py +10 -0
  30. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/connection.py +123 -105
  31. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/permissions.py +14 -5
  32. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/session_state.py +7 -6
  33. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/tool_calls.py +17 -2
  34. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/exceptions.py +4 -0
  35. agent_client_protocol-1.0.0/src/acp/experimental/__init__.py +13 -0
  36. agent_client_protocol-1.0.0/src/acp/experimental/negotiation.py +214 -0
  37. agent_client_protocol-1.0.0/src/acp/experimental/v2/__init__.py +18 -0
  38. agent_client_protocol-1.0.0/src/acp/experimental/v2/_connection.py +24 -0
  39. agent_client_protocol-1.0.0/src/acp/experimental/v2/_initialization.py +67 -0
  40. agent_client_protocol-1.0.0/src/acp/experimental/v2/_methods.py +58 -0
  41. agent_client_protocol-1.0.0/src/acp/experimental/v2/_params.py +65 -0
  42. agent_client_protocol-1.0.0/src/acp/experimental/v2/_router.py +74 -0
  43. agent_client_protocol-1.0.0/src/acp/experimental/v2/_schema_base.py +60 -0
  44. agent_client_protocol-1.0.0/src/acp/experimental/v2/agent.py +287 -0
  45. agent_client_protocol-1.0.0/src/acp/experimental/v2/client.py +463 -0
  46. agent_client_protocol-1.0.0/src/acp/experimental/v2/interfaces.py +314 -0
  47. agent_client_protocol-1.0.0/src/acp/experimental/v2/meta.py +41 -0
  48. agent_client_protocol-1.0.0/src/acp/experimental/v2/schema.py +6018 -0
  49. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/http/__init__.py +1 -1
  50. agent_client_protocol-1.0.0/src/acp/http/asgi.py +97 -0
  51. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/http/client.py +41 -13
  52. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/http/protocol.py +10 -1
  53. agent_client_protocol-1.0.0/src/acp/http/server.py +374 -0
  54. agent_client_protocol-1.0.0/src/acp/interfaces.py +468 -0
  55. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/meta.py +2 -1
  56. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/router.py +64 -9
  57. agent_client_protocol-1.0.0/src/acp/schema.py +5846 -0
  58. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/stdio.py +1 -1
  59. agent_client_protocol-1.0.0/src/acp/task/__init__.py +4 -0
  60. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/task/sender.py +1 -5
  61. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/transports.py +2 -8
  62. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/utils.py +89 -92
  63. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/ws/client.py +10 -3
  64. agent_client_protocol-1.0.0/src/acp/ws/server.py +75 -0
  65. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/conftest.py +18 -0
  66. agent_client_protocol-1.0.0/tests/contrib/test_contrib_permissions.py +118 -0
  67. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/contrib/test_contrib_session_state.py +60 -0
  68. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/contrib/test_contrib_tool_calls.py +5 -2
  69. agent_client_protocol-1.0.0/tests/golden/session_update_compaction_summary_chunk.json +8 -0
  70. agent_client_protocol-1.0.0/tests/golden/session_update_compaction_update.json +11 -0
  71. agent_client_protocol-1.0.0/tests/http/test_asgi.py +208 -0
  72. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_fixes.py +59 -14
  73. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_http_client.py +97 -20
  74. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_http_server.py +127 -18
  75. agent_client_protocol-1.0.0/tests/http/test_loopback.py +255 -0
  76. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_sse.py +19 -0
  77. agent_client_protocol-1.0.0/tests/http/test_transport_errors.py +133 -0
  78. agent_client_protocol-1.0.0/tests/http/test_v2_loopback.py +226 -0
  79. agent_client_protocol-1.0.0/tests/http/test_websocket.py +451 -0
  80. agent_client_protocol-1.0.0/tests/real_user/test_stdio_shutdown.py +69 -0
  81. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_connection_recovery.py +86 -2
  82. agent_client_protocol-1.0.0/tests/test_core.py +43 -0
  83. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_elicitation_catchall.py +6 -2
  84. agent_client_protocol-1.0.0/tests/test_gen_all.py +217 -0
  85. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_golden.py +107 -88
  86. agent_client_protocol-1.0.0/tests/test_param_model.py +126 -0
  87. agent_client_protocol-1.0.0/tests/test_protocol_method_coverage.py +220 -0
  88. agent_client_protocol-1.0.0/tests/test_protocol_negotiation.py +239 -0
  89. agent_client_protocol-1.0.0/tests/test_protocol_router.py +179 -0
  90. agent_client_protocol-1.0.0/tests/test_request_cancellation.py +459 -0
  91. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_request_error_logging.py +43 -31
  92. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_rpc.py +222 -10
  93. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_unstable.py +13 -1
  94. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_utils.py +9 -0
  95. agent_client_protocol-1.0.0/tests/test_v2_routing.py +141 -0
  96. agent_client_protocol-1.0.0/tests/test_v2_runtime.py +584 -0
  97. agent_client_protocol-1.0.0/tests/test_v2_schema.py +50 -0
  98. agent_client_protocol-0.12.0/scripts/gen_meta.py +0 -43
  99. agent_client_protocol-0.12.0/scripts/gen_schema.py +0 -1096
  100. agent_client_protocol-0.12.0/scripts/gen_signature.py +0 -169
  101. agent_client_protocol-0.12.0/src/acp/agent/router.py +0 -119
  102. agent_client_protocol-0.12.0/src/acp/client/connection.py +0 -293
  103. agent_client_protocol-0.12.0/src/acp/client/router.py +0 -179
  104. agent_client_protocol-0.12.0/src/acp/helpers.py +0 -280
  105. agent_client_protocol-0.12.0/src/acp/http/asgi.py +0 -175
  106. agent_client_protocol-0.12.0/src/acp/http/server.py +0 -461
  107. agent_client_protocol-0.12.0/src/acp/interfaces.py +0 -250
  108. agent_client_protocol-0.12.0/src/acp/schema.py +0 -6782
  109. agent_client_protocol-0.12.0/src/acp/task/__init__.py +0 -44
  110. agent_client_protocol-0.12.0/src/acp/task/dispatcher.py +0 -94
  111. agent_client_protocol-0.12.0/src/acp/task/queue.py +0 -67
  112. agent_client_protocol-0.12.0/src/acp/task/state.py +0 -84
  113. agent_client_protocol-0.12.0/src/acp/ws/server.py +0 -79
  114. agent_client_protocol-0.12.0/tests/contrib/test_contrib_permissions.py +0 -64
  115. agent_client_protocol-0.12.0/tests/http/test_loopback.py +0 -131
  116. agent_client_protocol-0.12.0/tests/http/test_websocket.py +0 -182
  117. agent_client_protocol-0.12.0/tests/test_core.py +0 -55
  118. agent_client_protocol-0.12.0/tests/test_gen_all.py +0 -204
  119. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/LICENSE +0 -0
  120. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/duet.py +0 -0
  121. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/scripts/__init__.py +0 -0
  122. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/agent/__init__.py +0 -0
  123. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/client/__init__.py +0 -0
  124. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/__init__.py +0 -0
  125. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/core.py +0 -0
  126. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/py.typed +0 -0
  127. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/task/supervisor.py +0 -0
  128. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/telemetry.py +0 -0
  129. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/ws/__init__.py +0 -0
  130. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/cancel_notification.json +0 -0
  131. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_audio.json +0 -0
  132. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_image.json +0 -0
  133. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_resource_blob.json +0 -0
  134. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_resource_link.json +0 -0
  135. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_resource_text.json +0 -0
  136. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_text.json +0 -0
  137. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/fs_read_text_file_request.json +0 -0
  138. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/fs_read_text_file_response.json +0 -0
  139. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/fs_write_text_file_request.json +0 -0
  140. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/initialize_request.json +0 -0
  141. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/initialize_response.json +0 -0
  142. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/new_session_request.json +0 -0
  143. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/new_session_response.json +0 -0
  144. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/permission_outcome_cancelled.json +0 -0
  145. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/permission_outcome_selected.json +0 -0
  146. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/prompt_request.json +0 -0
  147. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/request_permission_request.json +0 -0
  148. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/request_permission_response_selected.json +0 -0
  149. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_agent_message_chunk.json +0 -0
  150. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_agent_thought_chunk.json +0 -0
  151. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_config_option_update.json +0 -0
  152. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_plan.json +0 -0
  153. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call.json +0 -0
  154. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_edit.json +0 -0
  155. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_locations_rawinput.json +0 -0
  156. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_read.json +0 -0
  157. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_update_content.json +0 -0
  158. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_update_more_fields.json +0 -0
  159. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_user_message_chunk.json +0 -0
  160. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/set_session_config_option_request.json +0 -0
  161. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/set_session_config_option_response.json +0 -0
  162. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_content_text.json +0 -0
  163. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_diff.json +0 -0
  164. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_diff_no_old.json +0 -0
  165. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_terminal.json +0 -0
  166. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/conftest.py +0 -0
  167. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_cookies.py +0 -0
  168. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_protocol.py +0 -0
  169. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_ws_cookies.py +0 -0
  170. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/__init__.py +0 -0
  171. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_cancel_prompt_flow.py +0 -0
  172. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_mcp_servers_optional.py +0 -0
  173. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_permission_flow.py +0 -0
  174. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_stdio_limits.py +0 -0
  175. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_compatibility.py +0 -0
  176. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_deserialize.py +0 -0
  177. {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_gemini_example.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agent-client-protocol
3
- Version: 0.12.0
3
+ Version: 1.0.0
4
4
  Summary: A Python implement of Agent Client Protocol (ACP, by Zed Industries)
5
5
  Keywords: python
6
6
  Author-Email: Chojan Shang <psiace@apache.org>, Frost Ming <me@frostming.com>
@@ -12,18 +12,21 @@ Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
15
16
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
17
  Project-URL: Homepage, https://agentclientprotocol.github.io/python-sdk/
17
18
  Project-URL: Repository, https://github.com/agentclientprotocol/python-sdk
18
19
  Project-URL: Documentation, https://agentclientprotocol.github.io/python-sdk/
19
- Requires-Python: <3.15,>=3.10
20
+ Requires-Python: <3.16,>=3.10
20
21
  Requires-Dist: pydantic>=2.7
22
+ Requires-Dist: pydantic-core>=2.18.1
21
23
  Provides-Extra: logfire
22
24
  Requires-Dist: logfire>=0.14; extra == "logfire"
23
25
  Requires-Dist: opentelemetry-sdk>=1.28.0; extra == "logfire"
24
26
  Provides-Extra: http
25
- Requires-Dist: httpx[http2]>=0.27; extra == "http"
27
+ Requires-Dist: httpx2[http2]>=2.12; extra == "http"
26
28
  Requires-Dist: websockets>=12.0; extra == "http"
29
+ Requires-Dist: starlette>=0.49.3; extra == "http"
27
30
  Description-Content-Type: text/markdown
28
31
 
29
32
  <a href="https://agentclientprotocol.com/">
@@ -32,7 +35,7 @@ Description-Content-Type: text/markdown
32
35
 
33
36
  # Agent Client Protocol (Python)
34
37
 
35
- Build ACP-compliant agents and clients in Python with generated schema models, asyncio transports, helper builders, and runnable demos.
38
+ Build ACP-compliant agents and clients in Python with generated schema models, asyncio transports, and runnable demos.
36
39
 
37
40
  > Releases track the upstream ACP schema; contributions that tighten coverage or tooling are always welcome.
38
41
 
@@ -47,14 +50,14 @@ uv add agent-client-protocol
47
50
  ## At a glance
48
51
 
49
52
  - **Spec parity:** Generated Pydantic models in `acp.schema` track every ACP release so payloads stay valid.
53
+ - **Direct construction:** Generated discriminator fields have defaults, so models can be instantiated directly.
50
54
  - **Runtime ergonomics:** Async base classes, stdio JSON-RPC plumbing, and lifecycle helpers keep custom agents tiny.
51
55
  - **Examples ready:** Streaming, permissions, Gemini bridge, and duet demos live under `examples/`.
52
- - **Helper builders:** `acp.helpers` mirrors the Go/TS SDK APIs for content blocks, tool calls, and session updates.
53
56
  - **Contrib utilities:** Session accumulators, tool call trackers, and permission brokers share patterns from real deployments.
54
57
 
55
58
  ## Who benefits
56
59
 
57
- - Agent authors who need typed models, helper builders, and event-stream ergonomics for ACP-compatible assistants.
60
+ - Agent authors who need typed models and event-stream ergonomics for ACP-compatible assistants.
58
61
  - Client integrators embedding ACP parties inside Python applications or wrapping existing CLIs via stdio.
59
62
  - Tooling teams experimenting with permission flows, streaming UX, or Gemini bridges without re-implementing transports.
60
63
  See real adopters like kimi-cli in the [Use Cases list](https://agentclientprotocol.github.io/python-sdk/use-cases/).
@@ -80,7 +83,7 @@ See real adopters like kimi-cli in the [Use Cases list](https://agentclientproto
80
83
 
81
84
  ## Project layout
82
85
 
83
- - `src/acp/`: runtime package (agents, clients, transports, helpers, schema bindings, contrib utilities)
86
+ - `src/acp/`: runtime package (agents, clients, transports, schema bindings, contrib utilities)
84
87
  - `schema/`: upstream JSON schema sources (regenerate via `make gen-all`)
85
88
  - `docs/`: MkDocs content backing the published documentation
86
89
  - `examples/`: runnable scripts covering stdio orchestration patterns
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Agent Client Protocol (Python)
6
6
 
7
- Build ACP-compliant agents and clients in Python with generated schema models, asyncio transports, helper builders, and runnable demos.
7
+ Build ACP-compliant agents and clients in Python with generated schema models, asyncio transports, and runnable demos.
8
8
 
9
9
  > Releases track the upstream ACP schema; contributions that tighten coverage or tooling are always welcome.
10
10
 
@@ -19,14 +19,14 @@ uv add agent-client-protocol
19
19
  ## At a glance
20
20
 
21
21
  - **Spec parity:** Generated Pydantic models in `acp.schema` track every ACP release so payloads stay valid.
22
+ - **Direct construction:** Generated discriminator fields have defaults, so models can be instantiated directly.
22
23
  - **Runtime ergonomics:** Async base classes, stdio JSON-RPC plumbing, and lifecycle helpers keep custom agents tiny.
23
24
  - **Examples ready:** Streaming, permissions, Gemini bridge, and duet demos live under `examples/`.
24
- - **Helper builders:** `acp.helpers` mirrors the Go/TS SDK APIs for content blocks, tool calls, and session updates.
25
25
  - **Contrib utilities:** Session accumulators, tool call trackers, and permission brokers share patterns from real deployments.
26
26
 
27
27
  ## Who benefits
28
28
 
29
- - Agent authors who need typed models, helper builders, and event-stream ergonomics for ACP-compatible assistants.
29
+ - Agent authors who need typed models and event-stream ergonomics for ACP-compatible assistants.
30
30
  - Client integrators embedding ACP parties inside Python applications or wrapping existing CLIs via stdio.
31
31
  - Tooling teams experimenting with permission flows, streaming UX, or Gemini bridges without re-implementing transports.
32
32
  See real adopters like kimi-cli in the [Use Cases list](https://agentclientprotocol.github.io/python-sdk/use-cases/).
@@ -52,7 +52,7 @@ See real adopters like kimi-cli in the [Use Cases list](https://agentclientproto
52
52
 
53
53
  ## Project layout
54
54
 
55
- - `src/acp/`: runtime package (agents, clients, transports, helpers, schema bindings, contrib utilities)
55
+ - `src/acp/`: runtime package (agents, clients, transports, schema bindings, contrib utilities)
56
56
  - `schema/`: upstream JSON schema sources (regenerate via `make gen-all`)
57
57
  - `docs/`: MkDocs content backing the published documentation
58
58
  - `examples/`: runnable scripts covering stdio orchestration patterns
@@ -12,8 +12,6 @@ from acp import (
12
12
  PromptResponse,
13
13
  SetSessionModeResponse,
14
14
  run_agent,
15
- text_block,
16
- update_agent_message,
17
15
  )
18
16
  from acp.interfaces import Client
19
17
  from acp.schema import (
@@ -43,7 +41,7 @@ class ExampleAgent(Agent):
43
41
  self._conn = conn
44
42
 
45
43
  async def _send_agent_message(self, session_id: str, content: Any) -> None:
46
- update = content if isinstance(content, AgentMessageChunk) else update_agent_message(content)
44
+ update = content if isinstance(content, AgentMessageChunk) else AgentMessageChunk(content=content)
47
45
  await self._conn.session_update(session_id, update)
48
46
 
49
47
  async def initialize(
@@ -109,7 +107,7 @@ class ExampleAgent(Agent):
109
107
  if session_id not in self._sessions:
110
108
  self._sessions.add(session_id)
111
109
 
112
- await self._send_agent_message(session_id, text_block("Client sent:"))
110
+ await self._send_agent_message(session_id, TextContentBlock(text="Client sent:"))
113
111
  for block in prompt:
114
112
  await self._send_agent_message(session_id, block)
115
113
  return PromptResponse(stop_reason="end_turn")
@@ -12,7 +12,6 @@ from acp import (
12
12
  Client,
13
13
  RequestError,
14
14
  connect_to_agent,
15
- text_block,
16
15
  )
17
16
  from acp.core import ClientSideConnection
18
17
  from acp.schema import (
@@ -170,7 +169,7 @@ async def interactive_loop(conn: ClientSideConnection, session_id: str) -> None:
170
169
  try:
171
170
  await conn.prompt(
172
171
  session_id=session_id,
173
- prompt=[text_block(line)],
172
+ prompt=[TextContentBlock(text=line)],
174
173
  )
175
174
  except Exception as exc:
176
175
  logging.error("Prompt failed: %s", exc) # noqa: TRY400
@@ -1,5 +1,5 @@
1
1
  # /// script
2
- # requires-python = ">=3.10,<3.15"
2
+ # requires-python = ">=3.10,<3.16"
3
3
  # dependencies = [
4
4
  # "agent-client-protocol",
5
5
  # ]
@@ -14,11 +14,10 @@ from acp import (
14
14
  NewSessionResponse,
15
15
  PromptResponse,
16
16
  run_agent,
17
- text_block,
18
- update_agent_message,
19
17
  )
20
18
  from acp.interfaces import Client
21
19
  from acp.schema import (
20
+ AgentMessageChunk,
22
21
  AudioContentBlock,
23
22
  ClientCapabilities,
24
23
  EmbeddedResourceContentBlock,
@@ -70,7 +69,7 @@ class EchoAgent(Agent):
70
69
  ) -> PromptResponse:
71
70
  for block in prompt:
72
71
  text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "")
73
- chunk = update_agent_message(text_block(text))
72
+ chunk = AgentMessageChunk(content=TextContentBlock(text=text))
74
73
  chunk.field_meta = {"echo": True}
75
74
  chunk.content.field_meta = {"echo": True}
76
75
 
@@ -17,7 +17,6 @@ from acp import (
17
17
  Client,
18
18
  RequestError,
19
19
  connect_to_agent,
20
- text_block,
21
20
  )
22
21
  from acp.core import ClientSideConnection
23
22
  from acp.schema import (
@@ -254,7 +253,7 @@ def _print_text_content(content: object) -> None:
254
253
  async def _send_prompt(conn: ClientSideConnection, session_id: str, prompt: str, timeout: float | None) -> None:
255
254
  request = conn.prompt(
256
255
  session_id=session_id,
257
- prompt=[text_block(prompt)],
256
+ prompt=[TextContentBlock(text=prompt)],
258
257
  )
259
258
  if timeout is None:
260
259
  await request
@@ -1,5 +1,5 @@
1
1
  # /// script
2
- # requires-python = ">=3.10,<3.15"
2
+ # requires-python = ">=3.10,<3.16"
3
3
  # dependencies = [
4
4
  # "agent-client-protocol[http]",
5
5
  # ]
@@ -12,9 +12,10 @@ Start the server first (``uv run examples/http_server.py``), then run this.
12
12
  import asyncio
13
13
  from typing import Any
14
14
 
15
- from acp import connect_to_agent, text_block
15
+ from acp import connect_to_agent
16
16
  from acp.http import create_http_stream
17
17
  from acp.interfaces import Client
18
+ from acp.schema import TextContentBlock
18
19
 
19
20
 
20
21
  class ExampleClient(Client):
@@ -43,7 +44,7 @@ async def main() -> None:
43
44
  print(f"initialized (protocol v{init.protocol_version})")
44
45
  session = await conn.new_session(cwd=".", mcp_servers=[])
45
46
  print(f"session: {session.session_id}")
46
- result = await conn.prompt(session_id=session.session_id, prompt=[text_block("hello over http")])
47
+ result = await conn.prompt(session_id=session.session_id, prompt=[TextContentBlock(text="hello over http")])
47
48
  print(f"stop reason: {result.stop_reason}")
48
49
  finally:
49
50
  await conn.close()
@@ -1,5 +1,5 @@
1
1
  # /// script
2
- # requires-python = ">=3.10,<3.15"
2
+ # requires-python = ">=3.10,<3.16"
3
3
  # dependencies = [
4
4
  # "agent-client-protocol[http]",
5
5
  # "hypercorn>=0.17",
@@ -25,12 +25,10 @@ from acp import (
25
25
  InitializeResponse,
26
26
  NewSessionResponse,
27
27
  PromptResponse,
28
- text_block,
29
- update_agent_message,
30
28
  )
31
29
  from acp.http.asgi import create_asgi_app
32
30
  from acp.interfaces import Client
33
- from acp.schema import ClientCapabilities, Implementation
31
+ from acp.schema import AgentMessageChunk, ClientCapabilities, Implementation, TextContentBlock
34
32
 
35
33
 
36
34
  class EchoAgent(Agent):
@@ -56,12 +54,12 @@ class EchoAgent(Agent):
56
54
  text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "")
57
55
  await self._conn.session_update(
58
56
  session_id=session_id,
59
- update=update_agent_message(text_block(f"echo: {text}")),
57
+ update=AgentMessageChunk(content=TextContentBlock(text=f"echo: {text}")),
60
58
  )
61
59
  return PromptResponse(stop_reason="end_turn")
62
60
 
63
61
 
64
- # One agent instance per connection.
62
+ # A Starlette application with one agent instance per connection.
65
63
  app = create_asgi_app(lambda conn: EchoAgent())
66
64
 
67
65
 
@@ -1,5 +1,5 @@
1
1
  # /// script
2
- # requires-python = ">=3.10,<3.15"
2
+ # requires-python = ">=3.10,<3.16"
3
3
  # dependencies = [
4
4
  # "agent-client-protocol[http]",
5
5
  # ]
@@ -12,8 +12,9 @@ Start the server first (``uv run examples/http_server.py``), then run this.
12
12
  import asyncio
13
13
  from typing import Any
14
14
 
15
- from acp import connect_to_agent, text_block
15
+ from acp import connect_to_agent
16
16
  from acp.interfaces import Client
17
+ from acp.schema import TextContentBlock
17
18
  from acp.ws import create_websocket_stream
18
19
 
19
20
 
@@ -42,7 +43,9 @@ async def main() -> None:
42
43
  print(f"initialized (protocol v{init.protocol_version})")
43
44
  session = await conn.new_session(cwd=".", mcp_servers=[])
44
45
  print(f"session: {session.session_id}")
45
- result = await conn.prompt(session_id=session.session_id, prompt=[text_block("hello over websocket")])
46
+ result = await conn.prompt(
47
+ session_id=session.session_id, prompt=[TextContentBlock(text="hello over websocket")]
48
+ )
46
49
  print(f"stop reason: {result.stop_reason}")
47
50
  finally:
48
51
  await conn.close()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-client-protocol"
3
- version = "0.12.0"
3
+ version = "1.0.0"
4
4
  description = "A Python implement of Agent Client Protocol (ACP, by Zed Industries)"
5
5
  authors = [
6
6
  { name = "Chojan Shang", email = "psiace@apache.org" },
@@ -10,7 +10,7 @@ readme = "README.md"
10
10
  keywords = [
11
11
  "python",
12
12
  ]
13
- requires-python = ">=3.10,<3.15"
13
+ requires-python = ">=3.10,<3.16"
14
14
  classifiers = [
15
15
  "Intended Audience :: Developers",
16
16
  "Programming Language :: Python",
@@ -20,10 +20,12 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.12",
21
21
  "Programming Language :: Python :: 3.13",
22
22
  "Programming Language :: Python :: 3.14",
23
+ "Programming Language :: Python :: 3.15",
23
24
  "Topic :: Software Development :: Libraries :: Python Modules",
24
25
  ]
25
26
  dependencies = [
26
27
  "pydantic>=2.7",
28
+ "pydantic-core>=2.18.1",
27
29
  ]
28
30
 
29
31
  [project.urls]
@@ -37,13 +39,14 @@ logfire = [
37
39
  "opentelemetry-sdk>=1.28.0",
38
40
  ]
39
41
  http = [
40
- "httpx[http2]>=0.27",
42
+ "httpx2[http2]>=2.12",
41
43
  "websockets>=12.0",
44
+ "starlette>=0.49.3",
42
45
  ]
43
46
 
44
47
  [dependency-groups]
45
48
  dev = [
46
- "datamodel-code-generator>=0.25",
49
+ "datamodel-code-generator==0.81.0",
47
50
  "pytest>=7.2.0",
48
51
  "pytest-asyncio>=0.21.0",
49
52
  "tox-uv>=1.11.3",
@@ -55,9 +58,10 @@ dev = [
55
58
  "mkdocstrings[python]>=0.26.1",
56
59
  "python-dotenv>=1.1.1",
57
60
  "prek>=0.2.17",
58
- "httpx[http2]>=0.27",
61
+ "httpx2[http2]>=2.12",
59
62
  "websockets>=12.0",
60
63
  "uvicorn>=0.30",
64
+ "starlette>=0.49.3",
61
65
  ]
62
66
 
63
67
  [build-system]
@@ -122,6 +126,12 @@ ignore = [
122
126
  "src/acp/schema.py" = [
123
127
  "ALL",
124
128
  ]
129
+ "src/acp/experimental/v2/meta.py" = [
130
+ "ALL",
131
+ ]
132
+ "src/acp/experimental/v2/schema.py" = [
133
+ "ALL",
134
+ ]
125
135
 
126
136
  [tool.ruff.format]
127
137
  preview = true
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from importlib import import_module
5
+ from pathlib import Path
6
+ from typing import Protocol, cast
7
+
8
+ ROOT = Path(__file__).resolve().parents[1]
9
+
10
+ DEFAULT_PROTOCOL_VERSION = 1
11
+ SEMANTIC_MODULES = {
12
+ 1: "scripts.gen_schema_v1",
13
+ 2: "scripts.gen_schema_v2",
14
+ }
15
+ SUPPORTED_PROTOCOL_VERSIONS = tuple(SEMANTIC_MODULES)
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class SchemaSemantics:
20
+ schema_json: Path
21
+ version_file: Path
22
+ schema_out: Path
23
+ base_class: str
24
+ model_name_map: dict[str, str]
25
+ compatibility_aliases: str = ""
26
+ field_type_overrides: dict[str, str] = field(default_factory=dict)
27
+
28
+
29
+ class _SemanticsModule(Protocol):
30
+ SEMANTICS: SchemaSemantics
31
+
32
+
33
+ def get_schema_semantics(protocol_version: int) -> SchemaSemantics:
34
+ try:
35
+ module_name = SEMANTIC_MODULES[protocol_version]
36
+ except KeyError:
37
+ raise ValueError(f"Unsupported protocol version: {protocol_version}") from None
38
+ module = cast(_SemanticsModule, import_module(module_name))
39
+ return module.SEMANTICS
40
+
41
+
42
+ def inline_model_ref(definition: str, *steps: tuple[str, int | None]) -> str:
43
+ ref = f"#/$defs/{definition}"
44
+ for keyword, index in steps:
45
+ ref += f"#-datamodel-code-generator-#-{keyword}-#-special-#"
46
+ if index is not None:
47
+ ref += f"/{index}"
48
+ return ref
49
+
50
+
51
+ def variant_model_map(
52
+ definition: str,
53
+ keyword: str,
54
+ branch: str,
55
+ names: tuple[str, ...],
56
+ ) -> dict[str, str]:
57
+ return {inline_model_ref(definition, (keyword, index), (branch, None)): name for index, name in enumerate(names)}
@@ -18,13 +18,11 @@ if str(ROOT) not in sys.path:
18
18
  from scripts import gen_meta, gen_schema, gen_signature # noqa: E402 pylint: disable=wrong-import-position
19
19
 
20
20
  SCHEMA_DIR = ROOT / "schema"
21
- SCHEMA_JSON = SCHEMA_DIR / "schema.json"
22
- META_JSON = SCHEMA_DIR / "meta.json"
23
- VERSION_FILE = SCHEMA_DIR / "VERSION"
24
21
 
25
22
  DEFAULT_REPO = "agentclientprotocol/agent-client-protocol"
26
23
  LEGACY_SCHEMA_PATHS = ("schema/schema.unstable.json", "schema/meta.unstable.json")
27
24
  V1_SCHEMA_PATHS = ("schema/v1/schema.unstable.json", "schema/v1/meta.unstable.json")
25
+ V2_SCHEMA_PATHS = ("schema/v2/schema.unstable.json", "schema/v2/meta.unstable.json")
28
26
 
29
27
 
30
28
  def parse_args() -> argparse.Namespace:
@@ -42,6 +40,13 @@ def parse_args() -> argparse.Namespace:
42
40
  default=os.environ.get("ACP_SCHEMA_REPO", DEFAULT_REPO),
43
41
  help="Source repository providing schema.json/meta.json (default: %(default)s)",
44
42
  )
43
+ parser.add_argument(
44
+ "--protocol-version",
45
+ type=int,
46
+ choices=(1, 2),
47
+ default=int(os.environ.get("ACP_SCHEMA_PROTOCOL", "1")),
48
+ help="Protocol bindings to generate (default: %(default)s)",
49
+ )
45
50
  parser.add_argument(
46
51
  "--no-download",
47
52
  action="store_true",
@@ -67,23 +72,32 @@ def main() -> None:
67
72
 
68
73
  version = args.version or os.environ.get("ACP_SCHEMA_VERSION")
69
74
  repo = args.repo
70
- should_download = _should_download(args, version)
75
+ protocol_version = args.protocol_version
76
+ schema_dir = _schema_dir(protocol_version)
77
+ schema_json = schema_dir / "schema.json"
78
+ meta_json = schema_dir / "meta.json"
79
+ should_download = _should_download(args, version, protocol_version)
71
80
 
72
81
  if should_download:
73
82
  ref = resolve_ref(version)
74
- download_schema(repo, ref)
83
+ download_schema(repo, ref, protocol_version)
75
84
  else:
76
- ref = resolve_ref(version) if version else _cached_ref()
85
+ ref = resolve_ref(version) if version else _cached_ref(protocol_version)
77
86
 
78
- if not (SCHEMA_JSON.exists() and META_JSON.exists()):
79
- print("schema/schema.json or schema/meta.json missing; run with --version to fetch them.", file=sys.stderr)
87
+ if not (schema_json.exists() and meta_json.exists()):
88
+ print(
89
+ f"{schema_dir.relative_to(ROOT)} is missing schema.json or meta.json; run with --version.", file=sys.stderr
90
+ )
80
91
  sys.exit(1)
81
92
 
82
- gen_schema.generate_schema()
83
- gen_meta.generate_meta()
84
- gen_signature.gen_signature(ROOT / "src" / "acp")
93
+ gen_schema.generate_schema(protocol_version=protocol_version)
94
+ gen_meta.generate_meta(protocol_version=protocol_version)
95
+ if protocol_version == 1:
96
+ gen_signature.gen_signature(ROOT / "src" / "acp")
97
+ else:
98
+ gen_signature.gen_signature(ROOT / "src" / "acp" / "experimental" / "v2", protocol_version=2)
85
99
  if args.format_output:
86
- format_generated_files()
100
+ format_generated_files(protocol_version)
87
101
 
88
102
  if ref:
89
103
  print(f"Generated schema using ref: {ref}")
@@ -91,32 +105,44 @@ def main() -> None:
91
105
  print("Generated schema using local schema files")
92
106
 
93
107
 
94
- def format_generated_files() -> None:
95
- files = [
96
- ROOT / "src" / "acp" / "schema.py",
97
- ROOT / "src" / "acp" / "meta.py",
98
- ROOT / "src" / "acp" / "interfaces.py",
99
- ROOT / "src" / "acp" / "agent" / "connection.py",
100
- ROOT / "src" / "acp" / "client" / "connection.py",
101
- ]
108
+ def format_generated_files(protocol_version: int) -> None:
109
+ if protocol_version == 1:
110
+ files = [
111
+ ROOT / "src" / "acp" / "schema.py",
112
+ ROOT / "src" / "acp" / "meta.py",
113
+ ROOT / "src" / "acp" / "interfaces.py",
114
+ ROOT / "src" / "acp" / "agent" / "connection.py",
115
+ ROOT / "src" / "acp" / "client" / "connection.py",
116
+ ]
117
+ else:
118
+ files = [
119
+ ROOT / "src" / "acp" / "experimental" / "v2" / "schema.py",
120
+ ROOT / "src" / "acp" / "experimental" / "v2" / "meta.py",
121
+ ROOT / "src" / "acp" / "experimental" / "v2" / "interfaces.py",
122
+ ROOT / "src" / "acp" / "experimental" / "v2" / "agent.py",
123
+ ROOT / "src" / "acp" / "experimental" / "v2" / "client.py",
124
+ ]
102
125
  subprocess.check_call([sys.executable, "-m", "ruff", "check", "--fix", *(str(path) for path in files)]) # noqa: S603
103
126
  subprocess.check_call([sys.executable, "-m", "ruff", "format", *(str(path) for path in files)]) # noqa: S603
104
127
 
105
128
 
106
- def _should_download(args: argparse.Namespace, version: str | None) -> bool:
129
+ def _should_download(args: argparse.Namespace, version: str | None, protocol_version: int) -> bool:
130
+ schema_dir = _schema_dir(protocol_version)
131
+ schema_json = schema_dir / "schema.json"
132
+ meta_json = schema_dir / "meta.json"
107
133
  env_override = os.environ.get("ACP_SCHEMA_DOWNLOAD")
108
134
  if env_override is not None:
109
135
  return env_override.lower() in {"1", "true", "yes"}
110
136
  if args.no_download:
111
137
  return False
112
138
  if version:
113
- if not SCHEMA_JSON.exists() or not META_JSON.exists():
139
+ if not schema_json.exists() or not meta_json.exists():
114
140
  return True
115
- cached = _cached_ref()
141
+ cached = _cached_ref(protocol_version)
116
142
  if args.force:
117
143
  return True
118
144
  return cached != resolve_ref(version)
119
- return not (SCHEMA_JSON.exists() and META_JSON.exists())
145
+ return not (schema_json.exists() and meta_json.exists())
120
146
 
121
147
 
122
148
  def resolve_ref(version: str | None) -> str:
@@ -124,31 +150,35 @@ def resolve_ref(version: str | None) -> str:
124
150
  return "refs/heads/main"
125
151
  if version.startswith("refs/"):
126
152
  return version
127
- if re.fullmatch(r"schema-v\d+\.\d+\.\d+", version):
153
+ if re.fullmatch(r"schema-v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?", version):
128
154
  return f"refs/tags/{version}"
129
- if re.fullmatch(r"v?\d+\.\d+\.\d+", version):
155
+ if re.fullmatch(r"v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?", version):
130
156
  value = version if version.startswith("v") else f"v{version}"
131
157
  return f"refs/tags/{value}"
132
158
  return f"refs/heads/{version}"
133
159
 
134
160
 
135
- def download_schema(repo: str, ref: str) -> None:
136
- SCHEMA_DIR.mkdir(parents=True, exist_ok=True)
161
+ def download_schema(repo: str, ref: str, protocol_version: int = 1) -> None:
162
+ schema_dir = _schema_dir(protocol_version)
163
+ schema_json = schema_dir / "schema.json"
164
+ meta_json = schema_dir / "meta.json"
165
+ version_file = schema_dir / "VERSION"
166
+ schema_dir.mkdir(parents=True, exist_ok=True)
137
167
  try:
138
- schema_data, meta_data = fetch_schema_pair(repo, ref)
168
+ schema_data, meta_data = fetch_schema_pair(repo, ref, protocol_version)
139
169
  except RuntimeError as exc: # pragma: no cover - network error path
140
170
  print(exc, file=sys.stderr)
141
171
  sys.exit(1)
142
172
 
143
- SCHEMA_JSON.write_text(json.dumps(schema_data, indent=2) + "\n", encoding="utf-8")
144
- META_JSON.write_text(json.dumps(meta_data, indent=2) + "\n", encoding="utf-8")
145
- VERSION_FILE.write_text(ref + "\n", encoding="utf-8")
173
+ schema_json.write_text(json.dumps(schema_data, indent=2) + "\n", encoding="utf-8")
174
+ meta_json.write_text(json.dumps(meta_data, indent=2) + "\n", encoding="utf-8")
175
+ version_file.write_text(ref + "\n", encoding="utf-8")
146
176
  print(f"Fetched schema and meta from {repo}@{ref}")
147
177
 
148
178
 
149
- def fetch_schema_pair(repo: str, ref: str) -> tuple[dict, dict]:
179
+ def fetch_schema_pair(repo: str, ref: str, protocol_version: int = 1) -> tuple[dict, dict]:
150
180
  errors = []
151
- for schema_path, meta_path in schema_source_paths(ref):
181
+ for schema_path, meta_path in schema_source_paths(ref, protocol_version):
152
182
  schema_url = f"https://raw.githubusercontent.com/{repo}/{ref}/{schema_path}"
153
183
  meta_url = f"https://raw.githubusercontent.com/{repo}/{ref}/{meta_path}"
154
184
  try:
@@ -160,8 +190,10 @@ def fetch_schema_pair(repo: str, ref: str) -> tuple[dict, dict]:
160
190
  raise RuntimeError(f"Failed to fetch schema and meta from {repo}@{ref}. Attempts:\n{attempted}")
161
191
 
162
192
 
163
- def schema_source_paths(ref: str) -> tuple[tuple[str, str], ...]:
164
- if re.fullmatch(r"refs/tags/schema-v\d+\.\d+\.\d+", ref):
193
+ def schema_source_paths(ref: str, protocol_version: int = 1) -> tuple[tuple[str, str], ...]:
194
+ if protocol_version == 2:
195
+ return (V2_SCHEMA_PATHS,)
196
+ if re.fullmatch(r"refs/tags/schema-v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?", ref):
165
197
  return (V1_SCHEMA_PATHS, LEGACY_SCHEMA_PATHS)
166
198
  return (LEGACY_SCHEMA_PATHS, V1_SCHEMA_PATHS)
167
199
 
@@ -174,9 +206,18 @@ def fetch_json(url: str) -> dict:
174
206
  raise RuntimeError(f"Failed to fetch {url}: {exc}") from exc
175
207
 
176
208
 
177
- def _cached_ref() -> str | None:
178
- if VERSION_FILE.exists():
179
- return VERSION_FILE.read_text(encoding="utf-8").strip() or None
209
+ def _schema_dir(protocol_version: int) -> Path:
210
+ if protocol_version == 1:
211
+ return SCHEMA_DIR
212
+ if protocol_version == 2:
213
+ return SCHEMA_DIR / "v2"
214
+ raise ValueError(f"Unsupported protocol version: {protocol_version}")
215
+
216
+
217
+ def _cached_ref(protocol_version: int) -> str | None:
218
+ version_file = _schema_dir(protocol_version) / "VERSION"
219
+ if version_file.exists():
220
+ return version_file.read_text(encoding="utf-8").strip() or None
180
221
  return None
181
222
 
182
223