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.
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/PKG-INFO +10 -7
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/README.md +4 -4
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/agent.py +2 -4
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/client.py +1 -2
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/echo_agent.py +3 -4
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/gemini.py +1 -2
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/http_client.py +4 -3
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/http_server.py +4 -6
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/ws_client.py +6 -3
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/pyproject.toml +15 -5
- agent_client_protocol-1.0.0/scripts/_schema_semantics.py +57 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/scripts/gen_all.py +80 -39
- agent_client_protocol-1.0.0/scripts/gen_meta.py +96 -0
- agent_client_protocol-1.0.0/scripts/gen_schema.py +207 -0
- agent_client_protocol-1.0.0/scripts/gen_schema_v1.py +235 -0
- agent_client_protocol-1.0.0/scripts/gen_schema_v2.py +235 -0
- agent_client_protocol-1.0.0/scripts/gen_signature.py +266 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/__init__.py +58 -49
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_cookies.py +1 -1
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_deserialize.py +20 -1
- agent_client_protocol-1.0.0/src/acp/_jsonrpc.py +28 -0
- agent_client_protocol-1.0.0/src/acp/_protocol_adapters.py +72 -0
- agent_client_protocol-1.0.0/src/acp/_schema_base.py +79 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_sse.py +22 -8
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/_transport.py +18 -24
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/agent/connection.py +94 -7
- agent_client_protocol-1.0.0/src/acp/agent/router.py +10 -0
- agent_client_protocol-1.0.0/src/acp/client/connection.py +626 -0
- agent_client_protocol-1.0.0/src/acp/client/router.py +10 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/connection.py +123 -105
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/permissions.py +14 -5
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/session_state.py +7 -6
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/tool_calls.py +17 -2
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/exceptions.py +4 -0
- agent_client_protocol-1.0.0/src/acp/experimental/__init__.py +13 -0
- agent_client_protocol-1.0.0/src/acp/experimental/negotiation.py +214 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/__init__.py +18 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/_connection.py +24 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/_initialization.py +67 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/_methods.py +58 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/_params.py +65 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/_router.py +74 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/_schema_base.py +60 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/agent.py +287 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/client.py +463 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/interfaces.py +314 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/meta.py +41 -0
- agent_client_protocol-1.0.0/src/acp/experimental/v2/schema.py +6018 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/http/__init__.py +1 -1
- agent_client_protocol-1.0.0/src/acp/http/asgi.py +97 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/http/client.py +41 -13
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/http/protocol.py +10 -1
- agent_client_protocol-1.0.0/src/acp/http/server.py +374 -0
- agent_client_protocol-1.0.0/src/acp/interfaces.py +468 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/meta.py +2 -1
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/router.py +64 -9
- agent_client_protocol-1.0.0/src/acp/schema.py +5846 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/stdio.py +1 -1
- agent_client_protocol-1.0.0/src/acp/task/__init__.py +4 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/task/sender.py +1 -5
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/transports.py +2 -8
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/utils.py +89 -92
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/ws/client.py +10 -3
- agent_client_protocol-1.0.0/src/acp/ws/server.py +75 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/conftest.py +18 -0
- agent_client_protocol-1.0.0/tests/contrib/test_contrib_permissions.py +118 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/contrib/test_contrib_session_state.py +60 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/contrib/test_contrib_tool_calls.py +5 -2
- agent_client_protocol-1.0.0/tests/golden/session_update_compaction_summary_chunk.json +8 -0
- agent_client_protocol-1.0.0/tests/golden/session_update_compaction_update.json +11 -0
- agent_client_protocol-1.0.0/tests/http/test_asgi.py +208 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_fixes.py +59 -14
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_http_client.py +97 -20
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_http_server.py +127 -18
- agent_client_protocol-1.0.0/tests/http/test_loopback.py +255 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_sse.py +19 -0
- agent_client_protocol-1.0.0/tests/http/test_transport_errors.py +133 -0
- agent_client_protocol-1.0.0/tests/http/test_v2_loopback.py +226 -0
- agent_client_protocol-1.0.0/tests/http/test_websocket.py +451 -0
- agent_client_protocol-1.0.0/tests/real_user/test_stdio_shutdown.py +69 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_connection_recovery.py +86 -2
- agent_client_protocol-1.0.0/tests/test_core.py +43 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_elicitation_catchall.py +6 -2
- agent_client_protocol-1.0.0/tests/test_gen_all.py +217 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_golden.py +107 -88
- agent_client_protocol-1.0.0/tests/test_param_model.py +126 -0
- agent_client_protocol-1.0.0/tests/test_protocol_method_coverage.py +220 -0
- agent_client_protocol-1.0.0/tests/test_protocol_negotiation.py +239 -0
- agent_client_protocol-1.0.0/tests/test_protocol_router.py +179 -0
- agent_client_protocol-1.0.0/tests/test_request_cancellation.py +459 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_request_error_logging.py +43 -31
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_rpc.py +222 -10
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_unstable.py +13 -1
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_utils.py +9 -0
- agent_client_protocol-1.0.0/tests/test_v2_routing.py +141 -0
- agent_client_protocol-1.0.0/tests/test_v2_runtime.py +584 -0
- agent_client_protocol-1.0.0/tests/test_v2_schema.py +50 -0
- agent_client_protocol-0.12.0/scripts/gen_meta.py +0 -43
- agent_client_protocol-0.12.0/scripts/gen_schema.py +0 -1096
- agent_client_protocol-0.12.0/scripts/gen_signature.py +0 -169
- agent_client_protocol-0.12.0/src/acp/agent/router.py +0 -119
- agent_client_protocol-0.12.0/src/acp/client/connection.py +0 -293
- agent_client_protocol-0.12.0/src/acp/client/router.py +0 -179
- agent_client_protocol-0.12.0/src/acp/helpers.py +0 -280
- agent_client_protocol-0.12.0/src/acp/http/asgi.py +0 -175
- agent_client_protocol-0.12.0/src/acp/http/server.py +0 -461
- agent_client_protocol-0.12.0/src/acp/interfaces.py +0 -250
- agent_client_protocol-0.12.0/src/acp/schema.py +0 -6782
- agent_client_protocol-0.12.0/src/acp/task/__init__.py +0 -44
- agent_client_protocol-0.12.0/src/acp/task/dispatcher.py +0 -94
- agent_client_protocol-0.12.0/src/acp/task/queue.py +0 -67
- agent_client_protocol-0.12.0/src/acp/task/state.py +0 -84
- agent_client_protocol-0.12.0/src/acp/ws/server.py +0 -79
- agent_client_protocol-0.12.0/tests/contrib/test_contrib_permissions.py +0 -64
- agent_client_protocol-0.12.0/tests/http/test_loopback.py +0 -131
- agent_client_protocol-0.12.0/tests/http/test_websocket.py +0 -182
- agent_client_protocol-0.12.0/tests/test_core.py +0 -55
- agent_client_protocol-0.12.0/tests/test_gen_all.py +0 -204
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/LICENSE +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/examples/duet.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/scripts/__init__.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/agent/__init__.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/client/__init__.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/contrib/__init__.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/core.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/py.typed +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/task/supervisor.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/telemetry.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/src/acp/ws/__init__.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/cancel_notification.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_audio.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_image.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_resource_blob.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_resource_link.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_resource_text.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/content_text.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/fs_read_text_file_request.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/fs_read_text_file_response.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/fs_write_text_file_request.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/initialize_request.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/initialize_response.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/new_session_request.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/new_session_response.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/permission_outcome_cancelled.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/permission_outcome_selected.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/prompt_request.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/request_permission_request.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/request_permission_response_selected.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_agent_message_chunk.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_agent_thought_chunk.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_config_option_update.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_plan.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_edit.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_locations_rawinput.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_read.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_update_content.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_tool_call_update_more_fields.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/session_update_user_message_chunk.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/set_session_config_option_request.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/set_session_config_option_response.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_content_text.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_diff.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_diff_no_old.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/golden/tool_content_terminal.json +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/conftest.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_cookies.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_protocol.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/http/test_ws_cookies.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/__init__.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_cancel_prompt_flow.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_mcp_servers_optional.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_permission_flow.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/real_user/test_stdio_limits.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_compatibility.py +0 -0
- {agent_client_protocol-0.12.0 → agent_client_protocol-1.0.0}/tests/test_deserialize.py +0 -0
- {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.
|
|
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.
|
|
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:
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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=[
|
|
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.
|
|
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 =
|
|
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=[
|
|
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.
|
|
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
|
|
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=[
|
|
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.
|
|
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=
|
|
57
|
+
update=AgentMessageChunk(content=TextContentBlock(text=f"echo: {text}")),
|
|
60
58
|
)
|
|
61
59
|
return PromptResponse(stop_reason="end_turn")
|
|
62
60
|
|
|
63
61
|
|
|
64
|
-
#
|
|
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.
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
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 (
|
|
79
|
-
print(
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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
|
|
178
|
-
if
|
|
179
|
-
return
|
|
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
|
|