Rust SDK: add typed per-session capability controls#1455
Conversation
8899958 to
cb93071
Compare
cb93071 to
e59f4d0
Compare
Adds a typed `SessionCapability` enum and matching `SessionConfig` / `ResumeSessionConfig` fields plus builder methods, so callers can express "enable memory", "disable plan-mode", etc. as a per-session wire parameter rather than a spawn-time CLI flag. - `SessionCapability` is `#[non_exhaustive]`, kebab-case-serialized (via `Display` / `FromStr` / `From<&str>` / `From<String>`), and carries an `Other(String)` escape hatch for forward compatibility with capabilities the runtime adds without requiring an SDK rebuild. - `SessionConfig` and `ResumeSessionConfig` each gain `enabled_capabilities` / `disabled_capabilities` vectors and four builders: `with_enable_capability`, `with_disable_capability`, `with_enabled_capabilities`, `with_disabled_capabilities`. - `SessionConfig::into_wire` and `ResumeSessionConfig::into_wire` convert the vecs to `Option<Vec<String>>` and emit them as `enabledCapabilities` / `disabledCapabilities` in the `session.create` and `session.resume` JSON-RPC payloads. Empty vecs are serialised as `None` (field omitted). Disable wins over enable on conflict (the runtime applies enable first, then disable). - Works for every transport -- including `Transport::External` (Desktop app / shared CLI server) -- because it does not rely on CLI spawn arguments. Pairs with github/copilot-agent-runtime#8918 (per-session capability API) and github/agents#981 (Desktop missing memory capability). 10 new unit tests: 3 enum-level tests (Display / FromStr / From conversions) and 7 wire-serialisation tests in a dedicated `capability_tests` module in `types.rs` (empty omitted, single enable, single disable, bulk-replace, Other round-trip, resume empty, resume enable+disable). Pre-existing test breakage: rust/tests/session_test.rs and rust/tests/protocol_version_test.rs reference removed API methods on main and are unrelated to this change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e59f4d0 to
e205d4c
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| > [github/copilot-agent-runtime#8918](https://github.com/github/copilot-agent-runtime/pull/8918) | ||
| > or later. On older runtimes the fields are silently ignored. | ||
| > Pairs with [github/agents#1081](https://github.com/github/agents/issues/1081) | ||
| > (Desktop app missing memory capability). |
There was a problem hiding this comment.
Please avoid referring to closed-source repos anywhere in the open-source code.
There was a problem hiding this comment.
Dang it! Will excise, apologies.
There was a problem hiding this comment.
Replying on Morabbin's behalf: fixed in the latest pushes. The README/source docs and PR body no longer add closed-source repository references.
| /// SDK release can still be opted into without waiting for a new | ||
| /// enum variant. | ||
| /// | ||
| /// Requires github/copilot-agent-runtime#8918 or later. |
There was a problem hiding this comment.
Please avoid referring to closed source. There are a few comments like this.
There was a problem hiding this comment.
Fix incoming; removing all mentions from all parts of the PR
There was a problem hiding this comment.
Replying on Morabbin's behalf: fixed in the latest pushes. The README/source docs and PR body no longer add closed-source repository references.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| /// | ||
| /// Pass any kebab-case capability string here to forward it | ||
| /// verbatim to the runtime. | ||
| Other(String), |
There was a problem hiding this comment.
Rather than duplicating the list of capabilities, could we just directly use crate::generated::api_types::SessionCapability in the API? That would mean it automatically brings forwards the annotations for individual entries being experimental, plus avoids having to continually maintain this code whenever the set of capabilities changes.
I know that won't have an equivalent to Other(string) but the SDK requires a matching runtime version, so in general we don't need to provide forward compatibility for this situation.
There was a problem hiding this comment.
Additionally we plan soon to change session.create/session.resume to work via codegen instead of being hand-authored. At that point the set of capabilities would have to be a regular enum just like all the other enums we reference from those options (unless we also implement some nontrivial codegen changes across 6 languages).
There was a problem hiding this comment.
Sounds good to me; change incoming
There was a problem hiding this comment.
Replying on Morabbin's behalf: updated this to use the generated SessionCapability enum directly, re-exported from the crate root. The custom duplicated enum, Other(String), string conversions, and manual string serde are gone.
The create/resume config now stores generated SessionCapability values and the wire structs serialize those enum values directly. I also added coverage that SessionCapability::CanvasRenderer serializes to canvas-renderer, and reject SessionCapability::Unknown as invalid outbound config since it is only the generated deserialization fallback. This should line up with the planned generated create/resume path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| where | ||
| I: IntoIterator<Item = SessionCapability>, | ||
| { | ||
| self.enabled_capabilities = capabilities.into_iter().collect(); |
There was a problem hiding this comment.
Since with_enable_capability appends an item, I would have expected with_enable_capabilities also to append items. It seems surprising that it would also delete any existing items.
| - `enabledCapabilities` -- capabilities to opt the session into (extends the `SDK_CAPABILITIES` baseline) | ||
| - `disabledCapabilities` -- capabilities to opt the session out of (disable wins on overlap) | ||
|
|
||
| This approach works for every transport -- including `Transport::External` -- because it does not rely on CLI spawn arguments. |
There was a problem hiding this comment.
This is an implementation detail. Let's only document things that are relevant to SDK consumers.
| | `SessionStore` | `session-store` | Cross-session history tools and session-store metadata | | ||
| | `McpApps` | `mcp-apps` | MCP-Apps `ui://` resource passthrough (SEP-1865) | | ||
| | `CanvasRenderer` | `canvas-renderer` | Host-rendered extension canvases | | ||
| | `Unknown` | *(none)* | Deserialization fallback; rejected for outbound config | |
There was a problem hiding this comment.
It's probably best not to list the full set in the docs, because then we have to keep updating the docs (or by default we don't, and the docs get stale). The enum is already self-documenting.
| none of them are scheduled for removal. | ||
|
|
||
| - **`SessionCapability` enum** -- generated typed enum for per-session | ||
| capability opt-in / opt-out. Sent via `enabledCapabilities` / |
There was a problem hiding this comment.
Please don't document whether things are generated or not - that's an implementation detail and isn't relevant to SDK consumers.
| calls -- works for all transports including `Transport::External`. See | ||
| [Session capabilities](#session-capabilities) above. Marked | ||
| **experimental** via the repository's `<div class="warning">` rustdoc | ||
| convention. Node/Python/Go/.NET accept stringly-typed flags. |
There was a problem hiding this comment.
Node/Python/Go/.NET accept stringly-typed flags.
Is that true? Even if so there's no reason for the Rust docs to discuss that.
Summary
Adds typed
SessionConfig/ResumeSessionConfigfields and builder methods backed by the generatedSessionCapabilityenum, so callers can express per-session capability opt-in / opt-out without stringly-typed flags.This is a per-session API: the capability fields are sent as JSON-RPC wire parameters on
session.createandsession.resume, so it works for every transport includingTransport::External.Why
Some SDK consumers use
Transport::External, so spawn-time CLI flags have no effect. A per-session wire API is the correct approach for session-scoped runtime capabilities.What
SessionCapabilityenumgithub_copilot_sdk::SessionCapability.Memory,PlanMode,CanvasRenderer,Elicitation,McpApps,SessionStore,TuiHints,CliDocumentation,AskUser,InteractiveMode, andSystemNotifications.SessionCapability::Unknownas invalid outbound create/resume configuration, since that variant is only a deserialization fallback.SessionConfigandResumeSessionConfig(rust/src/types.rs)New fields:
enabled_capabilities: Vec<SessionCapability>-- opt this session into additional capabilities.disabled_capabilities: Vec<SessionCapability>-- opt this session out of capabilities; disable wins over enable on overlap.Four builders on each config type:
with_enable_capability,with_disable_capabilitywith_enabled_capabilities,with_disabled_capabilitiesWire serialization (
rust/src/wire.rs)SessionCreateWireandSessionResumeWiregain:enabledCapabilities?: SessionCapability[]disabledCapabilities?: SessionCapability[]Both fields are omitted from the wire when empty. The generated enum serde emits the protocol strings such as
memory,plan-mode, andcanvas-renderer.Runtime dependency
Requires a runtime version that supports
enabledCapabilitiesanddisabledCapabilities. On older runtimes the fields are silently ignored.Validation
cargo +nightly fmt --checkcargo clippy --lib --tests -- -D warningscargo test --lib(154 passed)cargo test --doc(20 passed, 30 ignored)cargo build