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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ agent-client-protocol-trace-viewer = { path = "src/agent-client-protocol-trace-v
yopo = { package = "agent-client-protocol-yopo", path = "src/yopo" }

# Protocol
agent-client-protocol-schema = { version = "=1.10.2", default-features = false, features = ["tracing"] }
agent-client-protocol-schema = { version = "=1.11.0", default-features = false, features = ["tracing"] }

# Core async runtime
tokio = { version = "1.52", default-features = false }
Expand Down
6 changes: 6 additions & 0 deletions md/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ agent-client-protocol = { version = "3", features = ["unstable_protocol_v2", "un
The `unstable` aggregate does not select draft v2; enable `unstable_protocol_v2`
explicitly when using that version.

Session notices and context compaction are stable in schema 1.11 and available
without feature flags in v1. Draft v2 only needs `unstable_protocol_v2` for these
surfaces. The former `unstable_session_notices` and `unstable_session_compaction`
SDK features have been removed. Applications still negotiate support through
the corresponding protocol capabilities.

The `unstable_subagents` feature exposes the schema's subagent capabilities,
updates, and session-message types. It is also included in `unstable`. The
existing `session/update` notification route carries subagent updates in v1 and,
Expand Down
24 changes: 22 additions & 2 deletions md/protocol-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ The reuse probe is conservative: if parsing and serializing the raw v2 request
would change any parameter, reuse is disabled and fallback opens a fresh
connection. That does not turn an otherwise valid v2 request into an error.

## Schema changes in schema 1.5 through 1.8
## Schema changes in schema 1.5 through 1.11

The `unstable_protocol_v2` API follows the moving draft schema. Schema 1.5 added
semantic newtypes for paths, media types, IDs, and cursors; renamed
Expand All @@ -462,7 +462,7 @@ Schema 1.6 adds `Cancelled` tool-call and plan-entry statuses to draft v2.

Schema 1.7 stabilizes elicitation and terminal authentication, so neither
surface requires its former SDK feature flag. It also adds context compaction
updates behind `unstable_session_compaction`; the SDK carries them through its
updates (stable since schema 1.11); the SDK carries them through its
existing typed `session/update` routing in both protocol versions. V1 clients
advertise compaction support through `ClientSessionCapabilities::compaction`.

Expand All @@ -472,3 +472,23 @@ versions. No tool-name-specific feature is required; draft v2 users only need
value unchanged on updates. In v2, omission leaves it unchanged, `null` clears
it, and a string replaces it. V1 cannot express the explicit v2 `null` clear
operation.

Schema 1.11 stabilizes session notices and compaction. No surface-specific
feature is required; draft v2 users still need `unstable_protocol_v2`.

In draft v2, failures after a prompt's user message is inserted end active work
with `StopReason::Error(ErrorStopReason)` on the idle state update, optionally
including a JSON-RPC error object. Failures before insertion remain error
responses to `session/prompt`. `StopReason` now serializes as an object tagged
by `stopReason`, flattened into `IdleStateUpdate`; its `Other` variant takes
`OtherStopReason` rather than a string and preserves unknown payload fields.
V1 prompt responses are unchanged and do not accept an `error` stop reason.

Schema 1.11 also rejects malformed payloads that receivers act on rather than
silently dropping invalid entries or substituting defaults. This includes
session setup's MCP servers and additional directories, terminal launch
arguments and environment, and v2 replay cursors. Typed request decoding
reports invalid parameters (`-32602`) without invoking the handler. `null`
still means empty for the defaultable acted-on lists; omission is accepted
only where the field is optional. Capability and display metadata retain their
lenient decoding.
4 changes: 0 additions & 4 deletions src/agent-client-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,14 @@ unstable = [
"unstable_llm_providers",
"unstable_mcp_over_acp",
"unstable_plan_operations",
"unstable_session_compaction",
"unstable_session_fork",
"unstable_session_notices",
"unstable_subagents",
]
unstable_end_turn_token_usage = ["agent-client-protocol-schema/unstable_end_turn_token_usage"]
unstable_llm_providers = ["agent-client-protocol-schema/unstable_llm_providers"]
unstable_mcp_over_acp = ["agent-client-protocol-schema/unstable_mcp_over_acp"]
unstable_plan_operations = ["agent-client-protocol-schema/unstable_plan_operations"]
unstable_session_compaction = ["agent-client-protocol-schema/unstable_session_compaction"]
unstable_session_fork = ["agent-client-protocol-schema/unstable_session_fork"]
unstable_session_notices = ["agent-client-protocol-schema/unstable_session_notices"]
unstable_subagents = ["agent-client-protocol-schema/unstable_subagents"]
unstable_protocol_v2 = ["agent-client-protocol-schema/unstable_protocol_v2"]

Expand Down
134 changes: 134 additions & 0 deletions src/agent-client-protocol/tests/schema_1_11.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use agent_client_protocol::{ErrorCode, JsonRpcMessage, schema::v1};
use serde_json::json;

#[test]
fn stable_notices_pass_through_typed_session_notifications() {
let capabilities = v1::ClientCapabilities::new()
.session(v1::ClientSessionCapabilities::new().notices(v1::NoticeCapabilities::new()));
assert_eq!(
serde_json::to_value(capabilities).unwrap()["session"]["notices"],
json!({})
);

let params = json!({
"sessionId": "session-1",
"update": {
"sessionUpdate": "notice",
"severity": "warning",
"title": "Provider degraded",
"description": "Retry later",
"_meta": { "source": "provider" }
}
});
let parsed = v1::AgentNotification::parse_message("session/update", &params).unwrap();
let v1::AgentNotification::SessionNotification(notification) = &parsed else {
panic!("expected a session notification");
};
assert!(matches!(notification.update, v1::SessionUpdate::Notice(_)));
assert_eq!(parsed.to_untyped_message().unwrap().params, params);

#[cfg(feature = "unstable_protocol_v2")]
{
use agent_client_protocol::schema::v2;
let parsed = v2::AgentNotification::parse_message("session/update", &params).unwrap();
let v2::AgentNotification::UpdateSessionNotification(notification) = &parsed else {
panic!("expected a v2 session notification");
};
assert!(matches!(notification.update, v2::SessionUpdate::Notice(_)));
assert_eq!(parsed.to_untyped_message().unwrap().params, params);
}
}

#[test]
fn acted_on_session_setup_lists_reject_invalid_params() {
for (field, malformed) in [
("additionalDirectories", json!(["/repo/lib", 42])),
("mcpServers", json!([{"name": "incomplete"}])),
] {
let mut params = json!({ "cwd": "/repo", "mcpServers": [] });
params[field] = malformed;
let error = v1::ClientRequest::parse_message("session/new", &params).unwrap_err();
assert_eq!(error.code, ErrorCode::InvalidParams);

#[cfg(feature = "unstable_protocol_v2")]
{
use agent_client_protocol::schema::v2;
let error = v2::ClientRequest::parse_message("session/new", &params).unwrap_err();
assert_eq!(error.code, ErrorCode::InvalidParams);
}
}

let params = json!({
"cwd": "/repo", "mcpServers": null, "additionalDirectories": null
});
let parsed = v1::NewSessionRequest::parse_message("session/new", &params).unwrap();
assert_eq!(parsed.mcp_servers, []);
assert_eq!(
parsed.additional_directories,
Vec::<std::path::PathBuf>::new()
);

#[cfg(feature = "unstable_protocol_v2")]
{
use agent_client_protocol::schema::v2;
let parsed = v2::NewSessionRequest::parse_message("session/new", &params).unwrap();
assert_eq!(parsed.mcp_servers, []);
assert_eq!(parsed.additional_directories, []);

let error = v2::ResumeSessionRequest::parse_message(
"session/resume",
&json!({ "sessionId": "session-1", "cwd": "/repo", "replayFrom": "start" }),
)
.unwrap_err();
assert_eq!(error.code, ErrorCode::InvalidParams);
}
}

#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn v2_error_and_custom_stop_reasons_pass_through_typed_notifications() {
use agent_client_protocol::schema::v2;

let reason = v2::StopReason::Error(
v2::ErrorStopReason::new()
.error(v2::Error::internal_error().data(json!({ "provider": "unavailable" }))),
);
let notification = v2::UpdateSessionNotification::new(
"session-1",
v2::SessionUpdate::StateUpdate(v2::StateUpdate::Idle(
v2::IdleStateUpdate::new().stop_reason(reason.clone()),
)),
);
let mut params = notification.to_untyped_message().unwrap().params;
assert_eq!(
params["update"],
json!({
"sessionUpdate": "state_update",
"state": "idle",
"stopReason": "error",
"error": {
"code": -32603,
"message": "Internal error",
"data": { "provider": "unavailable" }
}
})
);
let parsed = v2::UpdateSessionNotification::parse_message("session/update", &params).unwrap();
let v2::SessionUpdate::StateUpdate(v2::StateUpdate::Idle(idle)) = parsed.update else {
panic!("expected an idle state update");
};
assert_eq!(idle.stop_reason, Some(reason));

params["update"] = json!({
"sessionUpdate": "state_update", "state": "idle",
"stopReason": "_paused", "resumeAfter": 30,
"_meta": { "source": "provider" }
});
let parsed = v2::UpdateSessionNotification::parse_message("session/update", &params).unwrap();
assert_eq!(parsed.to_untyped_message().unwrap().params, params);

// V1 still reports prompt failures through the JSON-RPC response error.
assert!(
serde_json::from_value::<v1::PromptResponse>(json!({ "stopReason": "error" })).is_err()
);
}
2 changes: 0 additions & 2 deletions src/agent-client-protocol/tests/schema_session_compaction.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![cfg(feature = "unstable_session_compaction")]

use agent_client_protocol::JsonRpcMessage;
use agent_client_protocol::schema::v1::{
AgentNotification, ClientCapabilities, ClientSessionCapabilities, CompactionCapabilities,
Expand Down
Loading