From 833a25959a37a9a6f2b106d672c735320f7e4560 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 27 Jul 2026 18:02:30 +0200 Subject: [PATCH] feat(acp): expose unstable tool-call names --- md/protocol-v2.md | 10 +++- src/agent-client-protocol/CHANGELOG.md | 5 ++ src/agent-client-protocol/Cargo.toml | 4 +- .../tests/schema_tool_call_name.rs | 54 +++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 src/agent-client-protocol/tests/schema_tool_call_name.rs diff --git a/md/protocol-v2.md b/md/protocol-v2.md index 22e3f42..5b73f19 100644 --- a/md/protocol-v2.md +++ b/md/protocol-v2.md @@ -175,7 +175,7 @@ agent: - If the agent rejects the v2 initialize request, the error is surfaced. A rejected initialize is not treated as permission to retry with v1. -## Draft schema changes in schema 1.5 +## Draft schema changes in schema 1.5 and 1.6 The `unstable_protocol_v2` API follows the moving draft schema. Schema 1.5 adds semantic newtypes for paths, media types, IDs, and cursors; renames @@ -183,3 +183,11 @@ semantic newtypes for paths, media types, IDs, and cursors; renames types; and makes v1/v2 conversions fallible and generic. These are draft API changes rather than stable v1 wire changes. See [Migrating to v2.0](./migration_v2.0.md#draft-v2-schema-updates) for concrete source changes. + +Schema 1.6 adds `Cancelled` tool-call and plan-entry statuses to draft v2. +Programmatic tool-call names are available in both protocol versions through +the separate `unstable_tool_call_name` feature. Draft v2 users must enable both +`unstable_protocol_v2` and `unstable_tool_call_name`. In v2, an omitted name +leaves the existing value unchanged, `null` clears it, and a string replaces +it. V1 cannot clear an existing name, so converting a v2 `null` name to v1 +fails. diff --git a/src/agent-client-protocol/CHANGELOG.md b/src/agent-client-protocol/CHANGELOG.md index 70ce02e..0b0763f 100644 --- a/src/agent-client-protocol/CHANGELOG.md +++ b/src/agent-client-protocol/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added + +- *(unstable)* Expose programmatic tool-call names through the + `unstable_tool_call_name` feature. + ### Fixed - Allow the portable protocol engine and transport abstractions to build for diff --git a/src/agent-client-protocol/Cargo.toml b/src/agent-client-protocol/Cargo.toml index 57c0672..aaab4cb 100644 --- a/src/agent-client-protocol/Cargo.toml +++ b/src/agent-client-protocol/Cargo.toml @@ -19,19 +19,21 @@ rustdoc-args = ["--cfg", "docsrs"] default = [] # Forward unstable features from agent-client-protocol-schema. -# Enable these to get support for the corresponding unstable ACP methods. +# Enable these to get support for the corresponding unstable ACP surfaces. unstable = [ "unstable_auth_methods", "unstable_elicitation", "unstable_end_turn_token_usage", "unstable_mcp_over_acp", "unstable_session_fork", + "unstable_tool_call_name", ] unstable_auth_methods = ["agent-client-protocol-schema/unstable_auth_methods"] unstable_elicitation = ["agent-client-protocol-schema/unstable_elicitation"] unstable_end_turn_token_usage = ["agent-client-protocol-schema/unstable_end_turn_token_usage"] unstable_mcp_over_acp = ["agent-client-protocol-schema/unstable_mcp_over_acp"] unstable_session_fork = ["agent-client-protocol-schema/unstable_session_fork"] +unstable_tool_call_name = ["agent-client-protocol-schema/unstable_tool_call_name"] unstable_protocol_v2 = ["agent-client-protocol-schema/unstable_protocol_v2"] [dependencies] diff --git a/src/agent-client-protocol/tests/schema_tool_call_name.rs b/src/agent-client-protocol/tests/schema_tool_call_name.rs new file mode 100644 index 0000000..b0179bb --- /dev/null +++ b/src/agent-client-protocol/tests/schema_tool_call_name.rs @@ -0,0 +1,54 @@ +#![cfg(feature = "unstable_tool_call_name")] + +#[cfg(feature = "unstable_protocol_v2")] +use agent_client_protocol::schema::v1::ToolCallUpdate as V1ToolCallUpdate; +use agent_client_protocol::schema::v1::{ToolCall, ToolCallUpdateFields}; +use serde_json::json; + +#[test] +fn v1_tool_call_name_serializes_and_updates() { + let mut tool_call = ToolCall::new("call_1", "Read configuration").name("read_file"); + + assert_eq!( + serde_json::to_value(&tool_call).unwrap()["name"], + json!("read_file") + ); + + tool_call.update(ToolCallUpdateFields::new().name("write_file")); + assert_eq!(tool_call.name.as_deref(), Some("write_file")); + + tool_call.update(ToolCallUpdateFields::new()); + assert_eq!(tool_call.name.as_deref(), Some("write_file")); + + let null_name: ToolCallUpdateFields = serde_json::from_value(json!({ "name": null })).unwrap(); + tool_call.update(null_name); + assert_eq!(tool_call.name.as_deref(), Some("write_file")); +} + +#[cfg(feature = "unstable_protocol_v2")] +#[test] +fn tool_call_name_preserves_v2_patch_semantics() { + use agent_client_protocol::schema::{ + MaybeUndefined, + v2::{ + ToolCallUpdate, + conversion::{try_v1_to_v2, try_v2_to_v1}, + }, + }; + + let named: ToolCallUpdate = + try_v1_to_v2(ToolCall::new("call_1", "Read configuration").name("read_file")).unwrap(); + assert_eq!(named.name, MaybeUndefined::Value("read_file".to_string())); + + let omitted: ToolCallUpdate = + try_v1_to_v2(V1ToolCallUpdate::new("call_1", ToolCallUpdateFields::new())).unwrap(); + assert_eq!(omitted.name, MaybeUndefined::Undefined); + + let cleared = ToolCallUpdate::new("call_1").name(None::); + assert_eq!(cleared.name, MaybeUndefined::Null); + let error = try_v2_to_v1::<_, V1ToolCallUpdate>(cleared).unwrap_err(); + assert_eq!( + error.message(), + "v2 ToolCallUpdate.name with null value cannot be represented in v1" + ); +}