镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agent-client-protocol-schema/src/serde_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ mod default_on_null_tests {
$check::<v2::CloseSessionResponse>();
$check::<v2::ListSessionsRequest>();
$check::<v2::DeleteSessionResponse>();
$check::<v2::CancelToolCallResponse>();
$check::<v2::CancelTerminalResponse>();

#[cfg(feature = "unstable_auth_status")]
$check::<v2::AuthStatusRequest>();
Expand Down
275 changes: 274 additions & 1 deletion agent-client-protocol-schema/src/v2/agent.rs

Large diffs are not rendered by default.

418 changes: 418 additions & 0 deletions agent-client-protocol-schema/src/v2/client.rs

Large diffs are not rendered by default.

148 changes: 148 additions & 0 deletions agent-client-protocol-schema/src/v2/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,105 @@ impl TerminalExitStatus {
}
}

/// Client-initiated controls a terminal currently supports.
///
/// A malformed value falls back to no controls, so the Client never sends a request the
/// Agent did not advertise.
///
/// See protocol docs: [Cancelling Terminals](https://agentclientprotocol.com/protocol/v2/tool-calls#cancelling-terminals)
#[serde_as]
#[skip_serializing_none]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct TerminalCapabilities {
/// Permits the Client to cancel this terminal with `terminal/cancel`.
///
/// Omitted and `null` are equivalent and mean unsupported. An object (including `{}`)
/// means supported. Agents SHOULD stop advertising it once the terminal reports `exitStatus`.
#[serde_as(deserialize_as = "DefaultOnError")]
#[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
#[serde(default)]
pub cancel: Option<TerminalCancelCapabilities>,
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
/// Implementations MUST NOT assign meaning to keys they do not recognize.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
#[serde_as(deserialize_as = "DefaultOnError")]
#[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
#[serde(default)]
#[serde(rename = "_meta")]
pub meta: Option<Meta>,
}

impl TerminalCapabilities {
/// Builds an empty capability set. Cancellation is not advertised.
#[must_use]
pub fn new() -> Self {
Self::default()
}

/// Sets or removes permission to cancel this terminal.
#[must_use]
pub fn cancel(mut self, cancel: impl IntoOption<TerminalCancelCapabilities>) -> Self {
self.cancel = cancel.into_option();
self
}

/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
/// do not recognize.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}

/// Capability to cancel a terminal with `terminal/cancel`.
///
/// Supplying `{}` advertises support. An omitted or `null` `cancel` does not.
#[serde_as]
#[skip_serializing_none]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct TerminalCancelCapabilities {
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
/// Implementations MUST NOT assign meaning to keys they do not recognize.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
#[serde_as(deserialize_as = "DefaultOnError")]
#[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
#[serde(default)]
#[serde(rename = "_meta")]
pub meta: Option<Meta>,
}

impl TerminalCancelCapabilities {
/// Builds an empty capability object advertising cancellation support.
#[must_use]
pub fn new() -> Self {
Self::default()
}

/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
/// do not recognize.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}

/// An upsert for the stored state of an agent-owned terminal.
///
/// Only `terminalId` is required. Other fields have patch
Expand Down Expand Up @@ -225,6 +324,14 @@ pub struct TerminalUpdate {
#[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
#[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
pub size: MaybeUndefined<TerminalSize>,
/// Client-initiated controls this terminal currently supports, such as cancellation.
///
/// Omission leaves the capabilities unchanged, `null` clears them, and an object replaces
/// the whole previous value. A new terminal starts with none.
#[serde_as(deserialize_as = "DefaultOnError")]
#[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
#[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
pub capabilities: MaybeUndefined<TerminalCapabilities>,
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
/// metadata to their interactions. Omitted leaves the stored metadata unchanged. `null`
/// clears it. An object patches the stored metadata by top-level key: keys it omits are
Expand Down Expand Up @@ -254,6 +361,7 @@ impl TerminalUpdate {
output: MaybeUndefined::Undefined,
exit_status: MaybeUndefined::Undefined,
size: MaybeUndefined::Undefined,
capabilities: MaybeUndefined::Undefined,
meta: MaybeUndefined::Undefined,
}
}
Expand Down Expand Up @@ -293,6 +401,16 @@ impl TerminalUpdate {
self
}

/// Sets, clears, or leaves unchanged the Client-initiated controls.
#[must_use]
pub fn capabilities(
mut self,
capabilities: impl IntoMaybeUndefined<TerminalCapabilities>,
) -> Self {
self.capabilities = capabilities.into_maybe_undefined();
self
}

/// Patches, clears, or leaves unchanged terminal metadata.
///
/// A concrete object patches stored metadata by top-level key. See
Expand Down Expand Up @@ -326,6 +444,9 @@ impl TerminalUpdate {
if !update.size.is_undefined() {
self.size = update.size;
}
if !update.capabilities.is_undefined() {
self.capabilities = update.capabilities;
}
apply_meta_update(&mut self.meta, update.meta);
}
}
Expand Down Expand Up @@ -427,6 +548,33 @@ impl TerminalOutputChunk {
#[cfg(test)]
mod tests {

#[test]
fn terminal_capabilities_patch_and_tolerate_malformed_values() {
use serde_json::json;
let update = TerminalUpdate::new("term_003")
.capabilities(TerminalCapabilities::new().cancel(TerminalCancelCapabilities::new()));
assert_eq!(
serde_json::to_value(&update).unwrap(),
json!({ "terminalId": "term_003", "capabilities": { "cancel": {} } })
);

let mut stored = update;
stored.apply_update(TerminalUpdate::new("term_003"));
assert!(stored.capabilities.is_value());
stored.apply_update(TerminalUpdate::new("term_003").capabilities(MaybeUndefined::Null));
assert!(stored.capabilities.is_null());

for bad in [json!("yes"), json!(1)] {
let parsed: TerminalUpdate =
serde_json::from_value(json!({ "terminalId": "term_003", "capabilities": bad }))
.unwrap();
assert!(parsed.capabilities.is_undefined());
}
let parsed: TerminalCapabilities =
serde_json::from_value(json!({ "cancel": "yes" })).unwrap();
assert_eq!(parsed.cancel, None);
}

#[test]
fn terminal_size_is_an_optional_patch_field() {
let update = TerminalUpdate::new("term_1").size(TerminalSize::new(120, 40));
Expand Down
Loading
Loading