Skip to content

Add Rust SDK (technical preview) - #1164

Merged
stephentoub merged 87 commits into
mainfrom
tclem/rust-sdk-release-prep
May 6, 2026
Merged

Add Rust SDK (technical preview)#1164
stephentoub merged 87 commits into
mainfrom
tclem/rust-sdk-release-prep

Conversation

@tclem

@tclem tclem commented Apr 29, 2026

Copy link
Copy Markdown
Member

Adds a Rust SDK alongside the existing Node, Python, Go, and .NET SDKs in this repo. Same JSON-RPC client model, same protocol, same session lifecycle — just in Rust.

Important

Technical preview. This is published as github-copilot-sdk = "0.1" (pre-1.0) and the public API is subject to breaking changes as we iterate. Pin to an exact version, expect churn, and please file issues for friction or missing parity.

See rust/README.md for the full overview, examples, and the build/test commands. Generated types follow the same schema-driven flow used by the other SDKs (scripts/codegen/rust.ts).

CI for the new crate runs in .github/workflows/rust-sdk-tests.yml.

  Generated via Copilot (Claude Opus 4.7) on behalf of @tclem

tclem and others added 5 commits April 28, 2026 13:27
Adds the Copilot Rust SDK (`copilot-sdk` crate) under `rust/`,
alongside Rust codegen plumbed into `scripts/codegen/` and CI under
`.github/workflows/rust-sdk-tests.yml`. The crate ships a JSON-RPC
client, session lifecycle management, system message transforms,
permission policy helpers, the `define_tool` adapter, and per-event
`SessionHandler`/`SessionHooks` traits.

Includes:

- 14 ported E2E scenarios under `rust/tests/` driving the replay-proxy
  harness, plus a hand-curated set of unit tests.
- A rust-coding-skill (`.github/skills/rust-coding-skill/`) capturing
  conventions for error handling, async/concurrency, tracing, and the
  intentional trait exceptions in the SDK's public API.
- Release tooling: `rust-publish-release.yml`, `RELEASING.md`, and
  protocol-version generation wired into the existing automation.
- `PermissionResult` extended with `Deferred` and `Custom` variants
  for richer permission decisions.

Public API is held at 0.1.0-pre. Marked protocol-evolving public enums
`#[non_exhaustive]` so additive variants stay non-breaking.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Christopher Schleiden <cschleiden@github.com>
Co-authored-by: David Dossett <25163139+daviddossett@users.noreply.github.com>
Co-authored-by: Devraj Mehta <devm33@github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Co-authored-by: Evan Boyle <EvanBoyle@users.noreply.github.com>
Co-authored-by: Jeremy Moseley <jemoseley@microsoft.com>
Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
- **Broadcast subscriptions for lifecycle and session events.**
  `Client::subscribe_lifecycle()` and `Session::subscribe()` return
  `tokio::sync::broadcast::Receiver`; dropping the receiver
  unsubscribes. Replaces the prior callback-based `Client::on`,
  `Client::on_event_type`, `Session::on`, and `Unsubscribe` API.
  Spawned consumer tasks isolate panics naturally.
- **`PermissionResult` gains `Deferred` and `Custom` variants.**
  `Deferred` lets handlers resolve a request asynchronously via
  `session.permissions.handlePendingPermissionRequest` (notification
  path only — falls back to `Approved` on the direct RPC path).
  `Custom(Value)` lets handlers send arbitrary response payloads
  beyond the standard `approve-once` / `reject` shapes.
- **`#[non_exhaustive]` on protocol-evolving public enums**
  (`PermissionResult`, `SessionLifecycleEventType`,
  `GitHubReferenceType`, others) so additive variants stay
  non-breaking.
- **`ToolHandlerRouter` overrides per-event `SessionHandler` methods**
  so consumers can call `router.on_external_tool(...)` directly
  without unwrapping `HandlerResponse`.
- **`define_tool` accepts bare `async fn` items** in addition to
  closures, matching `tower::service_fn` /
  `hyper::service::service_fn` conventions. Documented in rustdoc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ypes

Generated code emitted `pub session_id: String` for every schema field named
`sessionId` and likewise for `requestId`, leaving consumers with mixed types:
`Session::id()` returned `SessionId` but `session.events_subscribe()` events
exposed `session_id: String`. Same papercut for request IDs in permission and
elicitation event payloads.

The newtypes are `#[serde(transparent)]` so the wire format is unchanged. This
adds a property-name override map to `scripts/codegen/rust.ts` that maps
`sessionId`, `remoteSessionId`, and `requestId` to the hand-authored types in
`crate::types`, and emits the matching `use` statement in both generated
modules. `mc_session_id` (MCP protocol metadata, not a Copilot session) stays
as `String`.

After regeneration: 27 fields converted to `SessionId` (including the handoff
event's `remoteSessionId`) and 25 to `RequestId`. The existing `PartialEq<str>`
/ `PartialEq<String>` impls on both newtypes mean test code like

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
define_tool's Fn(P) -> Fut bound gave closures only the deserialized
arguments, leaving session_id, tool_call_id, and tool_name unreachable.
That blocked the helper for any tool that needs to scope DB lookups to
a session, emit per-tool-call telemetry, or stream UI updates back to
the originating session — patterns that hit dozens of sites across
realistic tool suites.

Change the closure bound to Fn(ToolInvocation, P) -> Fut. The arguments
are moved out via mem::take before deserialization, so there is no
clone cost on the hot path. Closures that don't need the metadata
write |_inv, params|.

Also add ToolInvocation::params<P>() so long-form impl ToolHandler
blocks can deserialize without naming serde_json directly:

    async fn call(&self, inv: ToolInvocation) -> Result<ToolResult, Error> {
        let params: MyParams = inv.params()?;
        // …use inv.session_id alongside params…
    }

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Node, Python, and .NET all expose ping with an optional message.
Go requires it only because Go has no Option type — Rust has one,
so the API should match the languages with the same expressive power
rather than the one without.

Change ping(&self, message: &str) to ping(&self, message: Option<&str>).
When None, the message field is omitted from the request payload
rather than sent as an empty string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 29, 2026 14:09
@tclem
tclem requested a review from a team as a code owner April 29, 2026 14:09
@tclem
tclem marked this pull request as draft April 29, 2026 14:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Rust SDK crate (copilot-sdk, library name copilot) to the monorepo, mirroring the existing SDKs’ JSON-RPC client/session model, and wires it into repo workflows and scenario coverage.

Changes:

  • Introduces the Rust SDK crate with protocol types, JSON-RPC transport, session/handler abstractions, examples, and tests.
  • Extends scenario samples and verification scripts to build/run Rust implementations alongside TS/Python/Go/C#.
  • Updates repo automation: codegen, just tasks, scenario-build CI, Rust SDK CI, and release/publish workflows.
Show a summary per file
File Description
test/scenarios/verify.sh Shows Rust status in the aggregated scenario runner UI.
test/scenarios/transport/tcp/verify.sh Builds/runs the Rust TCP transport scenario.
test/scenarios/transport/tcp/rust/Cargo.toml Rust TCP scenario crate manifest.
test/scenarios/transport/tcp/rust/src/main.rs Rust TCP sample using external host:port CLI server.
test/scenarios/transport/stdio/verify.sh Builds/runs the Rust stdio transport scenario.
test/scenarios/transport/stdio/rust/Cargo.toml Rust stdio scenario crate manifest.
test/scenarios/transport/stdio/rust/src/main.rs Rust stdio sample spawning the CLI child process.
test/scenarios/transport/stdio/README.md Documents Rust sample location/package name.
test/scenarios/tools/tool-overrides/verify.sh Builds/runs Rust tool override scenario.
test/scenarios/tools/tool-overrides/rust/Cargo.toml Rust tool-overrides scenario crate manifest (+derive).
test/scenarios/tools/tool-overrides/rust/src/main.rs Rust sample overriding built-in tool behavior.
test/scenarios/tools/tool-filtering/verify.sh Builds/runs Rust tool filtering scenario.
test/scenarios/tools/tool-filtering/rust/Cargo.toml Rust tool-filtering scenario crate manifest.
test/scenarios/tools/tool-filtering/rust/src/main.rs Rust sample limiting available tools.
test/scenarios/tools/skills/verify.sh Builds/runs Rust skills scenario.
test/scenarios/tools/skills/rust/Cargo.toml Rust skills scenario crate manifest.
test/scenarios/tools/skills/rust/src/main.rs Rust sample configuring skill directories + hooks.
test/scenarios/tools/no-tools/verify.sh Builds/runs Rust no-tools scenario.
test/scenarios/tools/no-tools/rust/Cargo.toml Rust no-tools scenario crate manifest.
test/scenarios/tools/no-tools/rust/src/main.rs Rust sample disabling tools + replacing system prompt.
test/scenarios/tools/mcp-servers/verify.sh Builds/runs Rust MCP servers scenario.
test/scenarios/tools/mcp-servers/rust/Cargo.toml Rust mcp-servers scenario crate manifest.
test/scenarios/tools/mcp-servers/rust/src/main.rs Rust sample passing MCP servers config to CLI.
test/scenarios/tools/custom-agents/verify.sh Builds/runs Rust custom agents scenario.
test/scenarios/tools/custom-agents/rust/Cargo.toml Rust custom-agents scenario crate manifest (+derive).
test/scenarios/tools/custom-agents/rust/src/main.rs Rust sample defining custom agents + custom tool.
test/scenarios/sessions/streaming/verify.sh Builds/runs Rust streaming scenario.
test/scenarios/sessions/streaming/rust/Cargo.toml Rust streaming scenario crate manifest.
test/scenarios/sessions/streaming/rust/src/main.rs Rust sample counting streaming delta events.
test/scenarios/sessions/session-resume/verify.sh Builds/runs Rust session resume scenario.
test/scenarios/sessions/session-resume/rust/Cargo.toml Rust session-resume scenario crate manifest.
test/scenarios/sessions/session-resume/rust/src/main.rs Rust sample creating + resuming session by ID.
test/scenarios/sessions/infinite-sessions/verify.sh Builds/runs Rust infinite sessions scenario.
test/scenarios/sessions/infinite-sessions/rust/Cargo.toml Rust infinite-sessions scenario crate manifest.
test/scenarios/sessions/infinite-sessions/rust/src/main.rs Rust sample exercising infinite session thresholds.
test/scenarios/sessions/concurrent-sessions/verify.sh Builds/runs Rust concurrent sessions scenario.
test/scenarios/sessions/concurrent-sessions/rust/Cargo.toml Rust concurrent-sessions scenario crate manifest.
test/scenarios/sessions/concurrent-sessions/rust/src/main.rs Rust sample running two sessions concurrently.
test/scenarios/prompts/system-message/verify.sh Builds/runs Rust system-message scenario.
test/scenarios/prompts/system-message/rust/Cargo.toml Rust system-message scenario crate manifest.
test/scenarios/prompts/system-message/rust/src/main.rs Rust sample replacing system message.
test/scenarios/prompts/reasoning-effort/verify.sh Builds/runs Rust reasoning-effort scenario.
test/scenarios/prompts/reasoning-effort/rust/Cargo.toml Rust reasoning-effort scenario crate manifest.
test/scenarios/prompts/reasoning-effort/rust/src/main.rs Rust sample setting reasoning_effort.
test/scenarios/modes/default/verify.sh Builds/runs Rust default mode scenario.
test/scenarios/modes/default/rust/Cargo.toml Rust default-mode scenario crate manifest.
test/scenarios/modes/default/rust/src/main.rs Rust sample using default tool-enabled mode.
test/scenarios/callbacks/user-input/verify.sh Builds/runs Rust user-input callback scenario.
test/scenarios/callbacks/user-input/rust/Cargo.toml Rust user-input scenario crate manifest.
test/scenarios/callbacks/user-input/rust/src/main.rs Rust sample handling ask_user prompts.
test/scenarios/callbacks/permissions/verify.sh Builds/runs Rust permission callback scenario.
test/scenarios/callbacks/permissions/rust/Cargo.toml Rust permissions scenario crate manifest.
test/scenarios/callbacks/permissions/rust/src/main.rs Rust sample logging/approving permissions.
test/scenarios/callbacks/hooks/verify.sh Builds/runs Rust hooks callback scenario.
test/scenarios/callbacks/hooks/rust/Cargo.toml Rust hooks scenario crate manifest.
test/scenarios/callbacks/hooks/rust/src/main.rs Rust sample implementing SessionHooks logging.
test/scenarios/RUST_COVERAGE.md Documents Rust scenario parity coverage/gaps.
scripts/codegen/package.json Adds Rust generation to codegen scripts.
nodejs/scripts/update-protocol-version.ts Generates Rust SDK protocol version constant.
justfile Adds Rust format/lint/test/codegen tasks.
.gitignore Ignores Rust scenario build artifacts/lockfiles.
.github/workflows/scenario-builds.yml Adds CI job building all Rust scenario crates.
.github/workflows/rust-sdk-tests.yml Adds Rust SDK CI (fmt/clippy/doc/test/semver-checks).
.github/workflows/rust-release-pr.yml Adds release-plz workflow to open Rust release PRs.
.github/workflows/rust-publish-release.yml Adds release-plz workflow to publish Rust crate.
.github/workflows/codegen-check.yml Ensures Rust codegen + protocol version regen in CI.
.github/skills/rust-coding-skill/SKILL.md Adds repo-specific Rust engineering guidance.
.github/skills/rust-coding-skill/examples.md Adds Rust SDK examples/patterns for contributors.
.github/copilot-instructions.md Updates repo guidance to include Rust SDK + Rust skill.
rust/Cargo.toml Defines the new copilot-sdk crate (features, deps, MSRV).
rust/Cargo.lock Locks Rust dependencies for deterministic builds.
rust/README.md Documents Rust SDK usage, architecture, and API surface.
rust/CHANGELOG.md Establishes initial changelog and release-plz plan.
rust/RELEASING.md Documents release/publish operations for maintainers.
rust/LICENSE Rust crate license file.
rust/rust-toolchain.toml Pins Rust toolchain version/components for the crate.
rust/release-plz.toml Configures release-plz behavior for the Rust crate.
rust/clippy.toml Configures Rust clippy rules (e.g., disallowed macros).
rust/.rustfmt.toml Stable rustfmt config (edition 2024).
rust/.rustfmt.nightly.toml Nightly rustfmt config enabling unstable formatting opts.
rust/.gitignore Ignores Rust target dir and backup lock files.
rust/build.rs Build-time CLI bundling/extraction codegen support.
rust/src/sdk_protocol_version.rs Generated SDK protocol version constant for Rust.
rust/src/generated/mod.rs Rust generated-type module root + re-exports.
rust/src/jsonrpc.rs Content-Length framed JSON-RPC transport implementation.
rust/src/router.rs Per-session routing of notifications/requests.
rust/src/handler.rs Session handler traits/events + default handlers.
rust/src/permission.rs Permission policy wrappers over SessionHandler.
rust/src/transforms.rs System message transform extension point + dispatcher.
rust/src/session.rs Session lifecycle/event loop plumbing (core runtime).
rust/tests/jsonrpc_test.rs Tests for JSON-RPC framing/routing (feature-gated).
rust/tests/protocol_version_test.rs Tests protocol version negotiation behavior.
rust/tests/integration_test.rs Ignored integration tests against real CLI.
rust/examples/chat.rs Interactive streaming chat example.
rust/examples/hooks.rs Hooks example for logging/auditing.
rust/examples/tool_server.rs Tool server example (feature-gated on derive).
rust/examples/lifecycle_observer.rs Observer example for lifecycle/session event streams.

Copilot's findings

  • Files reviewed: 102/107 changed files
  • Comments generated: 5

Comment thread rust/src/session.rs Outdated
Comment thread rust/build.rs
Comment thread rust/README.md Outdated
Comment thread test/scenarios/sessions/streaming/verify.sh
Comment thread .github/workflows/scenario-builds.yml
tclem and others added 5 commits April 29, 2026 07:24
cargo doc was running with --features test-support, which left the
derive feature off and made intra-doc links to define_tool and
schema_for resolve to nothing — failing under the crate's
deny(rustdoc::broken_intra_doc_links).

docs.rs already uses all-features (see Cargo.toml's
[package.metadata.docs.rs]); align CI with that so the docs job
matches what users will see on docs.rs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  emitted from the loop correlate to a session in traces. Matches
  the pattern documented in the rust-coding-skill.

- README.md / embeddedcli.rs: correct the embedded-CLI documentation
  to match what build.rs and embeddedcli.rs actually do — archives
  come from the github/copilot-cli GitHub Releases, integrity is
  SHA-256 against SHA256SUMS.txt, and the runtime cache path is
  ~/.cache/copilot-sdk-{version}/copilot.

- test/scenarios/sessions/streaming/verify.sh: drop a duplicate
  '# Go: build' comment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Picks up the new model.call_failure session event (with its
ModelCallFailureData payload and ModelCallFailureSource enum) and
the new optional 'tip' field on session_info.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes path triggers and the regenerate step for other languages'
protocol-version files. Those drift checks are a pre-existing gap on
main and out of scope for the Rust SDK port.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tclem and others added 2 commits April 29, 2026 07:41
The 23-line setup checklist duplicated content already in
rust/RELEASING.md. One-line pointer is enough.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two scenarios still used the old `Fn(P) -> Fut` shape and broke when
the SDK switched to `Fn(ToolInvocation, P) -> Fut`. They don't use
the invocation field, so just bind it as `_inv`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1164 · ● 2.5M

Comment thread rust/src/session.rs Outdated
Cross-SDK consistency: every other SDK (Node, Python, Go, .NET) uses
`send`/`Send`/`SendAsync` plus `MessageOptions` as the public
parameter type. Rust was the outlier with `send_message` and
`SendOptions`, and the asymmetry with the existing `send_and_wait`
method made it read awkwardly.

- Rename `Session::send_message` -> `Session::send` (and the private
  helper `send_message_inner` -> `send_inner`).
- Rename the public `SendOptions` type -> `MessageOptions`.
- Delete the previous wire-level `MessageOptions` struct: it had no
  internal callers (the wire payload is hand-rolled in send_inner) and
  freeing the name was the cleanest path to parity.

Pre-1.0 type rename, no protocol or behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread .github/skills/rust-coding-skill/SKILL.md
Comment thread .github/skills/rust-coding-skill/SKILL.md
Comment thread .github/copilot-instructions.md Outdated
Comment thread rust/src/session.rs
Comment thread rust/src/lib.rs
Comment thread rust/src/types.rs Outdated
Comment thread rust/src/session.rs Outdated
Comment thread rust/src/types.rs
Comment thread rust/Cargo.toml Outdated
Comment thread rust/Cargo.toml Outdated
@stephentoub

Copy link
Copy Markdown
Collaborator

Thanks for getting this up.

It'd be helpful to run an agent over the other SDKs and this new one to look for any inconsistencies / gaps / divergences, so that we can then evaluate each and decide whether it's acceptable or should be addressed. The more consistent we can be across the SDKs, the easier it'll be to maintain them moving forward, the more information / docs about one will translate to consumption of the others, the better we'll be able to evolve with reduced concerns for how something we want to add may not fit well in a particular SDK, etc.

@github-actions

This comment has been minimized.

Previously Session::subscribe and Client::subscribe_lifecycle returned
raw tokio::sync::broadcast::Receiver<T> values. A survey of mature Rust
crates (tonic, lapin, rdkafka, redis-rs, tokio-tungstenite, iroh-gossip,
tokio-stream's BroadcastStream itself) found that none of them expose a
raw broadcast::Receiver in their public API; the dominant pattern is a
named newtype implementing futures::Stream, with overflow surfaced
explicitly in the item type.

Introduce a copilot::subscription module with:

  - EventSubscription / LifecycleSubscription newtypes
  - Inherent recv() returning Result<T, RecvError> for existing
    while-let loop ergonomics
  - Stream impl yielding Result<T, Lagged> so callers can use
    tokio_stream::StreamExt or futures::StreamExt combinators
  - Lagged / RecvError types owned by the SDK so consumers no longer
    import tokio's broadcast error types

Net effect: the channel choice is now an internal implementation detail.
We can swap broadcast for async-broadcast / flume / a custom backpressure
policy, or convert lag into an Event::Lagged variant, without a breaking
change to the public surface.

Existing while-let loops in tests and examples continue to compile and
behave identically: close and lag both exit the loop, matching
tokio::sync::broadcast::Receiver.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@tclem
tclem marked this pull request as ready for review April 29, 2026 16:19
@tclem
tclem marked this pull request as draft April 29, 2026 16:20
Local cargo +nightly fmt --check passed without `--config-path
.rustfmt.nightly.toml`, but CI runs with the explicit config and
flagged two diffs: import group flattening and test-mod import order.
Applied with the same flags CI uses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1164 · ● 1.9M

Comment thread rust/src/types.rs
/// Force-fail resume if the session does not exist on disk, instead of
/// silently starting a new session.
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_resume: Option<bool>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency gap: continue_pending_work missing from ResumeSessionConfig

All other SDKs expose a continuePendingWork / continue_pending_work / ContinuePendingWork field on their resume config, but the Rust ResumeSessionConfig doesn't include it:

SDK Field
Node.js continuePendingWork?: boolean
Python continue_pending_work: bool
Go ContinuePendingWork bool
.NET bool? ContinuePendingWork
Rust ❌ missing

This field is important for the "pending work resume" pattern — when a session is abandoned mid-tool-execution (e.g. via force_stop), setting continue_pending_work: true on resume instructs the CLI to replay and complete those pending tool/permission requests.

Suggested addition (after disable_resume):

/// When `true`, instructs the runtime to continue any pending tool calls
/// or permission requests that were outstanding when the session was
/// last disconnected. Use in combination with [`Client::force_stop`] to
/// hand off a live session to a new client without losing in-flight work.
/// See [`SessionConfig`] for the equivalent field on initial session creation.
#[serde(skip_serializing_if = "Option::is_none")]
pub continue_pending_work: Option<bool>,

And a corresponding builder method in impl ResumeSessionConfig:

pub fn with_continue_pending_work(mut self, value: bool) -> Self {
    self.continue_pending_work = Some(value);
    self
}

Don't forget to include it in ResumeSessionConfig::new (as None) and in the Debug impl.

Cross-SDK parity gap caught by the SDK consistency reviewer. All
four other SDKs (Node, Python, Go, .NET) expose this field; Rust
omitted it.

The field opts the runtime into continuing any tool calls or
permission requests that were pending when the previous connection
was dropped — it's the key enabler of the pending-work-handoff
pattern used together with `Client::force_stop` to migrate a session
from one process to another without losing in-flight work.

Plumbing:

- `pub continue_pending_work: Option<bool>` on `ResumeSessionConfig`
  with `#[serde(skip_serializing_if = "Option::is_none")]`. Container
  `rename_all = "camelCase"` covers the wire-name `continuePendingWork`.
  No manual payload-construction code is needed; resume_session
  serializes config -> wire via `serde_json::to_value(&config)`.
- `with_continue_pending_work(bool)` builder.
- Default impl: `None`.
- Debug impl: includes the field.

Tests:

- `resume_session_config_builder_composes` extended to cover the
  new field.
- `resume_session_config_serializes_continue_pending_work_to_camel_case`
  asserts the wire shape (`continuePendingWork: true`) and that
  unset values are omitted (skip_serializing_if).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1164 · ● 2.8M

Comment thread rust/README.md
`AutoModeSwitchResponse::{Yes, YesAlways, No}`. Default impl declines.
Cross-SDK parity is post-release follow-up — Node / Python / Go / .NET
consumers currently observe the request as a raw event and must drive
the wire response themselves.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Rust-only API" section documents on_auto_mode_switch as a Rust-only typed handler with a note that "Cross-SDK parity is post-release follow-up." The on_exit_plan_mode handler on SessionHandler is in the same situation — it's a typed Rust-only callback for exit_plan_mode.requested (other SDKs expose this only as a raw SessionEvent with no typed dispatch path), but it isn't mentioned here.

Worth adding a parallel bullet for on_exit_plan_mode so consumers have the same explicit heads-up that they would for on_auto_mode_switch:

- **`SessionHandler::on_exit_plan_mode`** — typed handler for the CLI's
  plan-mode exit prompt (`exit_plan_mode.requested`). Returns
  `ExitPlanModeResult` (default: approved with no action). Cross-SDK parity
  is post-release follow-up — Node / Python / Go / .NET consumers currently
  observe the request as a raw event and must drive the wire response
  themselves.

Minor documentation gap only — no code change needed.

Closes three cross-SDK parity gaps surfaced by the post-merge audit
against upstream commits 662f270, d3abfa2, and 180ca47.

ClientOptions::copilot_home: Option<PathBuf>
- Override the CLI's data directory. Exported as COPILOT_HOME to the
  spawned CLI process. Mirrors Node's copilotHome / Python's
  copilot_home.

ClientOptions::tcp_connection_token: Option<String>
- Optional auth token for TCP transport. Sent in the new `connect`
  JSON-RPC handshake (with backward-compat fall-back to `ping` for
  legacy CLI servers) and exported as COPILOT_CONNECTION_TOKEN to
  spawned CLI processes. When the SDK spawns its own CLI in TCP mode
  and this is unset, a UUID is generated so the loopback listener is
  safe by default. Combining with Transport::Stdio returns
  Error::InvalidConfig from Client::start. Mirrors Node's
  tcpConnectionToken / Python's tcp_connection_token / .NET's
  TcpConnectionToken.
- New `connect` handshake: verify_protocol_version now calls `connect`
  first, falling back to `ping` on -32601 MethodNotFound. The handshake
  carries the effective connection token. Matches Node's
  internalRpc.connect({ token }) sequence in client.ts.
- Adds `uuid` build-dep (1.x with v4 feature) for the auto-generated
  loopback token. Cryptographically-secure source via getrandom (already
  in the transitive dep tree).

SessionConfig::instruction_directories + ResumeSessionConfig::
instruction_directories: Option<Vec<PathBuf>>
- Additional directories the CLI searches for custom instruction files,
  distinct from skill_directories. Pure passthrough on the wire.
  Mirrors Node/Python instructionDirectories.

Error::InvalidConfig(String)
- New #[non_exhaustive] variant for client-construction errors that
  surface from Client::start (token + stdio, empty token, etc).

ClientInner gains effective_connection_token: Option<String> populated
in start(); from_transport now takes the token through (8 args ->
suppressed clippy::too_many_arguments on the internal helper).

Tests:
- protocol_version_test: existing tests updated for the new
  connect-then-ping fallback flow (server responds MethodNotFound on
  connect to exercise the legacy compat path).
- protocol_version_test::connect_handshake_supplies_protocol_version:
  new positive-path test — server responds to connect with a
  protocolVersion.
- lib::tests::build_command_sets_copilot_home_env_when_configured
- lib::tests::build_command_sets_connection_token_env_when_configured
- lib::tests::start_rejects_token_with_stdio_transport
- lib::tests::start_rejects_empty_connection_token

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

tclem and others added 3 commits May 4, 2026 07:45
…rage

Two related changes:

1. Drop the `uuid = "1"` dep added in 02780f0; replace with a
   `generate_connection_token()` helper that pulls 16 bytes from
   `getrandom::getrandom` and hex-encodes them. Same 128 bits of CSPRNG
   entropy, no semantic mismatch (the connection token is an opaque
   secret, not an identifier — typing it as a UUID would conflict with
   the existing pre-1.0 review consensus that schema-shaped IDs stay
   `String`). Output is a 32-char lowercase hex string, distinguishable
   from UUIDs at a glance.

   Rustdoc on the helper documents the decision so a future reader
   doesn't re-litigate it.

2. Address four reviewer items flagged on the github-app sync PR
   (#4399), all of which routed back upstream because src/ and tests/
   are mirrored from copilot-sdk:

   a. `session_config_serializes_instruction_directories_to_camel_case`
      — wire-shape test that `with_instruction_directories(...)`
      populates the field and serializes to `instructionDirectories`.

   b. `resume_session_config_serializes_instruction_directories_to_camel_case`
      — same on the resume path.

   c. `connect_handshake_forwards_explicit_token` and
      `connect_handshake_forwards_auto_generated_token` —
      positive coverage that `tcp_connection_token` actually reaches
      the outbound `connect` request's `token` param. Auto-generated
      case also asserts the shape (32-char lowercase hex) so a
      regression in the helper can't silently weaken loopback
      authentication.

   d. Rewrote the rustdoc on `Client::verify_protocol_version`. It
      claimed "sends a `ping` RPC", but the implementation now tries
      `connect` first and falls back to `ping` only on -32601. New
      docs describe the full handshake sequence and the token-
      forwarding semantics.

Test-support surface: adds two test-only entry points on `Client`
(`from_streams_with_connection_token`, `generate_connection_token_for_test`)
gated behind `cfg(any(test, feature = "test-support"))`, mirroring the
existing `from_streams_with_trace_provider` shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codegen was crashing on the post-1.0.41-0 schema with
`TypeError: s.split is not a function` at toPascalCase. Root cause:
the new `connect` JSON-RPC method's `ConnectResult.ok` field is
declared as `{ "type": "boolean", "enum": [true] }` (a single-value
boolean enum used as a "must-be-true" discriminant). The Rust
generator's `emitRustStringEnum` assumes string values, fed a
boolean, and panicked.

Python and Go generators already pre-process the schema with
`stripBooleanLiterals` (utils.ts:146) for exactly this reason —
upstream PR #1176 added the helper precisely because quicktype's
Python/Go renderers crashed on the same input. The TypeScript and
C# generators handle it natively. Rust's generator wasn't pre-
processing, so it inherited the same crash.

Fix: apply `stripBooleanLiterals` to both `apiSchema` and
`sessionEventsSchema` in `rust.ts` before postProcessSchema /
emit. Mirrors the Python/Go invocation pattern. Boolean
literal narrowing isn't expressible in Rust enums anyway —
the field stays `pub ok: bool`.

Regen produces two new types from the post-1.0.41-0 schema:
- `ConnectRequest` (token: Option<String>)
- `ConnectResult` (ok: bool, protocolVersion: u32)
- rpc_methods::CONNECT constant

These reflect the `connect` JSON-RPC method our hand-coded
verify_protocol_version handshake already invokes via
`client.call("connect", ...)`. We don't switch the call site to
the typed RPC because the existing untyped call matches how
`ping` is invoked next door, and the handshake needs the special
MethodNotFound fallback that doesn't fit the typed RPC abstraction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Now that codegen produces ConnectRequest / ConnectResult types
(b9b4d2b regen output), the hand-coded JSON-building in
Client::connect_handshake is just untyped boilerplate. Switches the
implementation to the typed `client.rpc().connect(ConnectRequest {
token })` call. Same wire shape; same MethodNotFound fallback at the
verify_protocol_version call site (lifted-out fallback, not
swallowed by the handshake helper); typed extraction of
protocol_version replaces the manual `value.get("protocolVersion")`
JSON walk.

ping() stays as a public hand-coded wrapper because the
`Option<&str>` ergonomics don't translate to the typed PingRequest
struct, and removing the public function would be a breaking API
change.

Test fixtures: extended the mock-server `connect` response stubs
in protocol_version_test.rs to include the now-required `version`
field on ConnectResult. Schema declares it required; deserialization
through the typed ConnectResult now enforces it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Pre-1.0 audit caught that `Client::send_telemetry` and
`Session::send_telemetry` were Rust-only public surface for an RPC
method (`sendTelemetry` / `session.sendTelemetry`) that:

- Is not described in `api.schema.json`.
- Is not exposed by the Node, Python, Go, or .NET SDKs (zero
  matches in their source trees).
- Has been moving around on the CLI side (the impl had a
  METHOD_NOT_FOUND fallback between top-level `sendTelemetry` and
  namespaced `server.sendTelemetry` to compensate).

A schema-undocumented + cross-SDK-absent public method is the
shape of an internal CLI knob, not a stable consumer API.
Consumers' canonical telemetry surface is the spawn-time env
injection via `ClientOptions::telemetry` (`TelemetryConfig`).
That stays.

Changes:
- Removed `Client::send_telemetry`, the cached
  `ServerTelemetryRpcMethod` enum, and the
  `ClientInner::server_telemetry_method` field that backed its
  fallback caching.
- Removed `Session::send_telemetry`.
- Removed `ServerTelemetryEvent` and `SessionTelemetryEvent`
  types from `types.rs`.
- Updated three integration tests in `tests/session_test.rs`
  (`send_telemetry_injects_payload_and_session_id`,
  `server_send_telemetry_sends_correct_payload`,
  `server_send_telemetry_falls_back_to_namespaced_method_and_caches_it`)
  by removing them, plus a fourth case in the dispatch-table
  test.
- Dropped now-unused local `METHOD_NOT_FOUND` const from
  the test file.
- Removed the "Rust-only API" README bullet describing the
  shortcut.
- Updated CHANGELOG to drop the type and method enumerations.

Net surface for telemetry: `ClientOptions::telemetry` +
`with_telemetry()` builder, plus the env-var injection in
`Client::build_command`. That's the documented, schema-described,
cross-SDK-consistent way to forward telemetry config to the CLI;
emitting individual telemetry events from consumer code is
intentionally out of scope.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Same audit shape as the send_telemetry removal: a sweep of public
methods that exist as Rust convenience wrappers on top of a typed
RPC, with no equivalent surface in the Node, Python, Go, or .NET
SDKs (zero matches across all four SDK source trees).

The methods are reachable cross-SDK through the typed `rpc()`
namespace — Rust callers simply switch from
`session.method()` to `session.rpc().<namespace>().<method>()`,
mirroring how Node/Python/Go/.NET consumers already drive these.

Removed from `Session`:
- get_model
- set_mode / get_mode
- set_name / get_name
- read_plan / update_plan / delete_plan
- list_workspace_files / read_workspace_file / create_workspace_file
- start_fleet
- set_approve_all_permissions
- call_rpc (generic forwarder; the typed rpc() namespace replaces it)

Removed from `Client`:
- get_quota (typed `client.rpc().account().get_quota()` already
  available everywhere including Rust)

Kept on Session: send, send_and_wait, abort, set_model, log,
disconnect/destroy, subscribe, capabilities, cancellation_token,
stop_event_loop, ui (and the field accessors id/cwd/workspace_path/
remote_url). These are either present in every SDK already (abort,
set_model, send/send_and_wait, log, disconnect) or are Rust-shape
helpers that don't have a typed RPC equivalent (subscribe,
cancellation_token, ui sub-API).

Updated:
- README "Rust-only API" section: dropped the "First-class Session
  convenience methods" bullet and the Client::get_quota bullet.
  What remains as Rust-only is now strictly language-shape items
  (newtypes, Transport enum, permission builders, from_streams,
  on_auto_mode_switch).
- CHANGELOG: dropped enumeration of the removed methods; restated
  what stays.
- 5 integration tests removed (get_name, set_name, list_workspace_files,
  read_workspace_file, create_workspace_file) plus the dispatch-table
  case for session.plan.delete.
- Unused imports cleaned up in session.rs.

Migration for the github-app consumer (the only known caller):
- session.set_approve_all_permissions(b)
  -> session.rpc().permissions().set_approve_all(
       PermissionsSetApproveAllRequest { enabled: b })
- session.set_mode("plan")
  -> session.rpc().mode().set(ModeSetRequest { mode: ... })
- session.read_plan()
  -> session.rpc().plan().read()
- (etc — all typed RPC namespace calls)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@stephentoub

Copy link
Copy Markdown
Collaborator

request_auto_mode_switch + on_auto_mode_switch handler —

Were these recently added in runtime? We haven't added anything for them yet in the other SDKs. Could we remove them from rust for now?

disabled_mcp_servers

Same here. This isn't exposed in any of the other SDKs.

If the desire ends up being to have this, we can add it across all of them, including Rust, in one fell swoop.

env_value_mode

Ditto. I'm not even sure what this is :)

Brought in 12 commits from origin/main, including CLI bumps to
1.0.41-0 and 1.0.41-1, plus upstream PR #966 ("Add provider model
and token limit overrides to ProviderConfig"). One trivial codegen
diff (single doc-comment update on `CustomAgentsUpdatedAgent.tools`
for the new "or null when all tools are available" semantics).

PR #966 added four new fields to ProviderConfig across all SDKs:
- `model_id: Option<String>` (well-known model ID for agent config
  + token limit lookup; falls back to SessionConfig::model)
- `wire_model: Option<String>` (model name sent to provider API for
  inference; falls back to model_id, then to SessionConfig::model)
- `max_prompt_tokens: Option<i64>` (overrides resolved model's
  default max prompt tokens; triggers compaction)
- `max_output_tokens: Option<i64>` (overrides resolved model's
  default max output tokens; truncates response)

Plus matching `with_*` builders. Wire-shape: camelCase
(`modelId`/`wireModel`/`maxPromptTokens`/`maxOutputTokens`),
skip_serializing_if when unset.

Extended `provider_config_builder_composes` test to exercise all
four fields and assert their wire shape (camelCase + omission).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@stephentoub stephentoub left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@stephentoub

Copy link
Copy Markdown
Collaborator

(We will follow-up in a subsequent PR addressing the remaining feedback.)

@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review — PR #1164 (Add Rust SDK)

This PR adds a brand-new Rust SDK. I reviewed it for cross-language API consistency with the Node.js, Python, Go, and .NET SDKs.

✅ Core API: well-aligned

The fundamental surface is consistent across all five SDKs:

Feature Node Python Go .NET Rust
create_session / resume_session
send / send_and_wait
get_messages, abort, set_model, log
disconnect / destroy
force_stop, continue_pending_work
ping, get_status, get_auth_status, list_models
list_sessions, get_session_metadata, delete_session
get_last_session_id, get/set_foreground_session_id
Hooks, transforms, tools, permissions, elicitation
Streaming, infinite sessions, telemetry, BYOK

The intentional divergences (typed newtypes, enum Transport, Client::from_streams, SessionFsProvider registration style, on_auto_mode_switch) are well-explained in the README's "Differences From Other SDKs" section. ✅

⚠️ One documentation bug found

The README's ### Session reference block (lines 107–124) documents several session methods as if they are direct convenience methods on Session, but they are not implemented on the struct — only session.rpc().X() equivalents exist:

  • session.get_model() — not implemented (use session.rpc().model().get_current())
  • session.get_mode() / session.set_mode() — not implemented (use session.rpc().mode().get()/set())
  • session.list_workspace_files() / session.read_workspace_file() — not implemented (use session.rpc().workspaces().*)
  • session.read_plan() / session.update_plan() — not implemented (use session.rpc().plan().*)
  • session.start_fleet() — not implemented (use session.rpc().fleet().start())

Since these are in rust,ignore blocks they aren't caught by cargo test --doc. An inline review comment has been left on the specific lines. The fix is either to implement the wrappers or update the code examples to use session.rpc().*.

This does not affect any other SDK — it's specific to the Rust README documentation.

Generated by SDK Consistency Review Agent for issue #1164 · ● 2.8M ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1164 · ● 2.8M

Comment thread rust/README.md
Comment on lines +107 to +127
// Model management
let model = session.get_model().await?;
session.set_model("claude-sonnet-4.5", None).await?;

// Mode management (interactive, plan, autopilot)
let mode = session.get_mode().await?;
session.set_mode("autopilot").await?;

// Workspace files
let files = session.list_workspace_files().await?;
let content = session.read_workspace_file("plan.md").await?;

// Plan management
let (exists, content) = session.read_plan().await?;
session.update_plan("Updated plan content").await?;

// Fleet (sub-agents)
session.start_fleet(Some("Implement the auth module")).await?;

// Cleanup (preserves on-disk session state for later resume)
session.disconnect().await?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation bug: several session.X() calls shown here don't exist on Session

Lines 108–124 document the following as direct convenience methods on Session, but none of them are actually implemented on the struct (only set_model and get_messages / abort are):

Shown in README Actual API
session.get_model() session.rpc().model().get_current().await?
session.get_mode() session.rpc().mode().get().await?
session.set_mode("autopilot") session.rpc().mode().set(ModeSetRequest { mode: "autopilot".into() }).await?
session.list_workspace_files() session.rpc().workspaces().list_files().await?
session.read_workspace_file("plan.md") session.rpc().workspaces().read_file(WorkspacesReadFileRequest { path: "plan.md".into() }).await?
session.read_plan() session.rpc().plan().read().await?
session.update_plan("...") session.rpc().plan().update(PlanUpdateRequest { ... }).await?
session.start_fleet(Some("...")) session.rpc().fleet().start(FleetStartRequest { ... }).await?

Because the code block uses rust,ignore, this isn't caught by cargo test --doc. A user copying these snippets from the README will get a compile error like no method named 'get_model' found for struct 'Session'.

Suggestion: Either implement these as ergonomic wrappers on Session (thin delegations to rpc()), or update this section to use session.rpc().* calls and add a note that the typed RPC namespace is the primary access point for these operations. If the wrappers are planned as a follow-up, it would be worth adding a // TODO: convenience wrapper note and linking to the "Typed RPC namespace" section so readers know the right path today.

@stephentoub
stephentoub added this pull request to the merge queue May 6, 2026
Merged via the queue into main with commit 19b4ad5 May 6, 2026
39 checks passed
@stephentoub
stephentoub deleted the tclem/rust-sdk-release-prep branch May 6, 2026 14:11
edburns pushed a commit that referenced this pull request Aug 4, 2026
* Add Rust SDK

Adds the Copilot Rust SDK (`copilot-sdk` crate) under `rust/`,
alongside Rust codegen plumbed into `scripts/codegen/` and CI under
`.github/workflows/rust-sdk-tests.yml`. The crate ships a JSON-RPC
client, session lifecycle management, system message transforms,
permission policy helpers, the `define_tool` adapter, and per-event
`SessionHandler`/`SessionHooks` traits.

Includes:

- 14 ported E2E scenarios under `rust/tests/` driving the replay-proxy
  harness, plus a hand-curated set of unit tests.
- A rust-coding-skill (`.github/skills/rust-coding-skill/`) capturing
  conventions for error handling, async/concurrency, tracing, and the
  intentional trait exceptions in the SDK's public API.
- Release tooling: `rust-publish-release.yml`, `RELEASING.md`, and
  protocol-version generation wired into the existing automation.
- `PermissionResult` extended with `Deferred` and `Custom` variants
  for richer permission decisions.

Public API is held at 0.1.0-pre. Marked protocol-evolving public enums
`#[non_exhaustive]` so additive variants stay non-breaking.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Christopher Schleiden <cschleiden@github.com>
Co-authored-by: David Dossett <25163139+daviddossett@users.noreply.github.com>
Co-authored-by: Devraj Mehta <devm33@github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Co-authored-by: Evan Boyle <EvanBoyle@users.noreply.github.com>
Co-authored-by: Jeremy Moseley <jemoseley@microsoft.com>
Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>

* Polish public API for 0.1.0 release

- **Broadcast subscriptions for lifecycle and session events.**
  `Client::subscribe_lifecycle()` and `Session::subscribe()` return
  `tokio::sync::broadcast::Receiver`; dropping the receiver
  unsubscribes. Replaces the prior callback-based `Client::on`,
  `Client::on_event_type`, `Session::on`, and `Unsubscribe` API.
  Spawned consumer tasks isolate panics naturally.
- **`PermissionResult` gains `Deferred` and `Custom` variants.**
  `Deferred` lets handlers resolve a request asynchronously via
  `session.permissions.handlePendingPermissionRequest` (notification
  path only — falls back to `Approved` on the direct RPC path).
  `Custom(Value)` lets handlers send arbitrary response payloads
  beyond the standard `approve-once` / `reject` shapes.
- **`#[non_exhaustive]` on protocol-evolving public enums**
  (`PermissionResult`, `SessionLifecycleEventType`,
  `GitHubReferenceType`, others) so additive variants stay
  non-breaking.
- **`ToolHandlerRouter` overrides per-event `SessionHandler` methods**
  so consumers can call `router.on_external_tool(...)` directly
  without unwrapping `HandlerResponse`.
- **`define_tool` accepts bare `async fn` items** in addition to
  closures, matching `tower::service_fn` /
  `hyper::service::service_fn` conventions. Documented in rustdoc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Route generated SessionId/RequestId fields through hand-authored newtypes

Generated code emitted `pub session_id: String` for every schema field named
`sessionId` and likewise for `requestId`, leaving consumers with mixed types:
`Session::id()` returned `SessionId` but `session.events_subscribe()` events
exposed `session_id: String`. Same papercut for request IDs in permission and
elicitation event payloads.

The newtypes are `#[serde(transparent)]` so the wire format is unchanged. This
adds a property-name override map to `scripts/codegen/rust.ts` that maps
`sessionId`, `remoteSessionId`, and `requestId` to the hand-authored types in
`crate::types`, and emits the matching `use` statement in both generated
modules. `mc_session_id` (MCP protocol metadata, not a Copilot session) stays
as `String`.

After regeneration: 27 fields converted to `SessionId` (including the handoff
event's `remoteSessionId`) and 25 to `RequestId`. The existing `PartialEq<str>`
/ `PartialEq<String>` impls on both newtypes mean test code like

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass ToolInvocation to define_tool closures

define_tool's Fn(P) -> Fut bound gave closures only the deserialized
arguments, leaving session_id, tool_call_id, and tool_name unreachable.
That blocked the helper for any tool that needs to scope DB lookups to
a session, emit per-tool-call telemetry, or stream UI updates back to
the originating session — patterns that hit dozens of sites across
realistic tool suites.

Change the closure bound to Fn(ToolInvocation, P) -> Fut. The arguments
are moved out via mem::take before deserialization, so there is no
clone cost on the hot path. Closures that don't need the metadata
write |_inv, params|.

Also add ToolInvocation::params<P>() so long-form impl ToolHandler
blocks can deserialize without naming serde_json directly:

    async fn call(&self, inv: ToolInvocation) -> Result<ToolResult, Error> {
        let params: MyParams = inv.params()?;
        // …use inv.session_id alongside params…
    }

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Make ping message argument optional

Node, Python, and .NET all expose ping with an optional message.
Go requires it only because Go has no Option type — Rust has one,
so the API should match the languages with the same expressive power
rather than the one without.

Change ping(&self, message: &str) to ping(&self, message: Option<&str>).
When None, the message field is omitted from the request payload
rather than sent as an empty string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Build Rust docs with all features in CI

cargo doc was running with --features test-support, which left the
derive feature off and made intra-doc links to define_tool and
schema_for resolve to nothing — failing under the crate's
deny(rustdoc::broken_intra_doc_links).

docs.rs already uses all-features (see Cargo.toml's
[package.metadata.docs.rs]); align CI with that so the docs job
matches what users will see on docs.rs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR #1164 review feedback

  emitted from the loop correlate to a session in traces. Matches
  the pattern documented in the rust-coding-skill.

- README.md / embeddedcli.rs: correct the embedded-CLI documentation
  to match what build.rs and embeddedcli.rs actually do — archives
  come from the github/copilot-cli GitHub Releases, integrity is
  SHA-256 against SHA256SUMS.txt, and the runtime cache path is
  ~/.cache/copilot-sdk-{version}/copilot.

- test/scenarios/sessions/streaming/verify.sh: drop a duplicate
  '# Go: build' comment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Regenerate Rust types for @github/copilot 1.0.39-0

Picks up the new model.call_failure session event (with its
ModelCallFailureData payload and ModelCallFailureSource enum) and
the new optional 'tip' field on session_info.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Scope codegen-check workflow changes to Rust only

Removes path triggers and the regenerate step for other languages'
protocol-version files. Those drift checks are a pre-existing gap on
main and out of scope for the Rust SDK port.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Point rust-publish-release workflow header to RELEASING.md

The 23-line setup checklist duplicated content already in
rust/RELEASING.md. One-line pointer is enough.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update Rust scenario binaries for new define_tool signature

Two scenarios still used the old `Fn(P) -> Fut` shape and broke when
the SDK switched to `Fn(ToolInvocation, P) -> Fut`. They don't use
the invocation field, so just bind it as `_inv`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename Session::send_message -> send and align MessageOptions

Cross-SDK consistency: every other SDK (Node, Python, Go, .NET) uses
`send`/`Send`/`SendAsync` plus `MessageOptions` as the public
parameter type. Rust was the outlier with `send_message` and
`SendOptions`, and the asymmetry with the existing `send_and_wait`
method made it read awkwardly.

- Rename `Session::send_message` -> `Session::send` (and the private
  helper `send_message_inner` -> `send_inner`).
- Rename the public `SendOptions` type -> `MessageOptions`.
- Delete the previous wire-level `MessageOptions` struct: it had no
  internal callers (the wire payload is hand-rolled in send_inner) and
  freeing the name was the cleanest path to parity.

Pre-1.0 type rename, no protocol or behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Wrap subscribe() in EventSubscription / LifecycleSubscription newtypes

Previously Session::subscribe and Client::subscribe_lifecycle returned
raw tokio::sync::broadcast::Receiver<T> values. A survey of mature Rust
crates (tonic, lapin, rdkafka, redis-rs, tokio-tungstenite, iroh-gossip,
tokio-stream's BroadcastStream itself) found that none of them expose a
raw broadcast::Receiver in their public API; the dominant pattern is a
named newtype implementing futures::Stream, with overflow surfaced
explicitly in the item type.

Introduce a copilot::subscription module with:

  - EventSubscription / LifecycleSubscription newtypes
  - Inherent recv() returning Result<T, RecvError> for existing
    while-let loop ergonomics
  - Stream impl yielding Result<T, Lagged> so callers can use
    tokio_stream::StreamExt or futures::StreamExt combinators
  - Lagged / RecvError types owned by the SDK so consumers no longer
    import tokio's broadcast error types

Net effect: the channel choice is now an internal implementation detail.
We can swap broadcast for async-broadcast / flume / a custom backpressure
policy, or convert lag into an Event::Lagged variant, without a breaking
change to the public surface.

Existing while-let loops in tests and examples continue to compile and
behave identically: close and lag both exit the loop, matching
tokio::sync::broadcast::Receiver.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Apply nightly rustfmt to subscription module

Local cargo +nightly fmt --check passed without `--config-path
.rustfmt.nightly.toml`, but CI runs with the explicit config and
flagged two diffs: import group flattening and test-mod import order.
Applied with the same flags CI uses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix workspaces RPC method names (was singular `workspace.*`)

`Session::list_workspace_files`, `read_workspace_file`, and
`create_workspace_file` were calling the singular `session.workspace.*`
RPC methods. The CLI exposes these under the plural namespace
`session.workspaces.*` (see Node `session.rpc.workspaces`, Go
`session.RPC.Workspaces`, .NET `session.Rpc.Workspaces`, Python
`session.rpc.workspaces`, and the generated `SESSION_WORKSPACES_*`
constants).

Route the three calls through the typed constants in
`generated::api_types::rpc_methods` so the bug class is structurally
gone — a stale string literal can no longer drift from the schema.

Add three integration tests against the mock server in
`session_test.rs` asserting the wire method name and request shape for
each helper. This mirrors the
`session_rpc_methods_send_correct_method_names` table-driven test that
already covers the rest of the session RPCs.

Also fold the missing `create_workspace_file` into the workspace
helpers bullet in CHANGELOG.md.

The bug never shipped — the SDK is pre-publish — so no migration
notes. Heads-up filed to the github-app sister copy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* rust: add typed RPC namespace, route helpers through it

Phase 4 § 4.6.A.5 — generate a fully-typed `client.rpc.*` /
`session.rpc.*` namespace from the Copilot CLI schema, mirroring the
Node/Python/Go/.NET SDKs. All hand-authored ergonomic helpers
(`list_workspace_files`, `read_plan`, `set_mode`, `list_models`,
`get_quota`, etc.) become one-line delegations over the namespace —
their public signatures are unchanged, but wire-method strings now
exist in exactly one place (`generated/rpc.rs`).

This makes the `session.workspace.*` → `session.workspaces.*` typo
bug class structurally impossible: helpers can't drift from the
schema because they no longer reference wire strings, and new RPCs
land in the namespace immediately as the schema regenerates.

- scripts/codegen/rust.ts: emit rust/src/generated/rpc.rs alongside
  api_types.rs. Builds a namespace tree from `rpcMethod` paths,
  resolves $ref/title/inline schemas for params + results, injects
  sessionId for session methods, dispatches via rpc_methods constants.
- rust/src/generated/rpc.rs: new ~1370 LOC generated file.
- rust/src/lib.rs: Client::rpc() accessor; ping/list_models/get_quota
  rewritten as delegations.
- rust/src/session.rs: Session::rpc() accessor; workspaces/plan/mode/
  model/name/log/fleet/permissions helpers rewritten as delegations.
- rust/tests/session_test.rs: add 3 namespace-only tests
  (session.rpc().agent().list, session.rpc().tasks().list,
  client.rpc().models().list); update session.log mock response to
  match typed LogResult shape (eventId required).
- rust/CHANGELOG.md, rust/README.md: document the new namespace.

cargo +nightly-2026-04-14 fmt --check, cargo clippy
--all-features --all-targets -- -D warnings, and cargo test
--all-features all green.

No breaking changes for SDK or github-app consumers — public helper
signatures are preserved by construction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename crate to `github-copilot-sdk`

Per formal naming decision, the published crate name on crates.io will
be `github-copilot-sdk` and the import path will be
`use github_copilot_sdk::...`.

Changes:
- `Cargo.toml`: `name = "github-copilot-sdk"`, `[lib] name = "github_copilot_sdk"`,
  `documentation = "https://docs.rs/github-copilot-sdk"`. Repository and
  homepage URLs continue to point at `github/copilot-sdk` (the repo
  name is unchanged).
- All `use copilot::` / `copilot::` references migrated to
  `use github_copilot_sdk::` / `github_copilot_sdk::` across `src/`,
  `tests/`, `examples/`, README, CHANGELOG, RELEASING.md, and codegen
  scripts.
- Embedded-CLI cache directory renamed from `~/.cache/copilot-sdk-*` to
  `~/.cache/github-copilot-sdk-*` for naming consistency. Pre-release
  caches will be orphaned; acceptable given the crate is unpublished.
- `release-plz.toml` package name updated.

Verified: `cargo check`, `cargo clippy -D warnings`, `cargo test`,
nightly `cargo fmt --check` all green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add typed wrappers for filter/MCP/permission shapes (Bucket A.1, A.3, A.4)

Replaces the remaining `serde_json::Value` blobs on the public surface
with typed structs/enums, mirroring the wire shapes documented in
Node/Go/Python/.NET. Helper signatures already changed from `Value` to
typed equivalents in the same call-sites; this lands the type
definitions and constructor-site updates.

A.3 — typed wrappers
* `SessionListFilter { cwd, git_root, repository, branch }` for
  `Client::list_sessions`. Was `Option<serde_json::Value>`.
* `McpServerConfig` tagged enum (Stdio/Http/Sse) with
  `McpStdioServerConfig` / `McpHttpServerConfig` payloads.
  `SessionConfig::mcp_servers`, `ResumeSessionConfig::mcp_servers`, and
  `CustomAgentConfig::mcp_servers` are now
  `Option<HashMap<String, McpServerConfig>>`. Accepts `type: "local"`
  alias on deserialize for back-compat with the legacy CLI shape.
* `PermissionRequestData` gains `kind: Option<PermissionRequestKind>`
  and `tool_call_id: Option<String>` fields. The eight CLI permission
  categories (shell/write/read/url/mcp/custom-tool/memory/hook) are
  enumerated; unknown kinds fall through to `Unknown`. Existing
  `extra: Value` flatten is preserved so `data.extra["command"]` etc.
  keep working.

A.4 — PermissionResult variants
* Adds `UserNotAvailable` (encodes as `{kind: "user-not-available"}`)
  and `NoResult` (encodes as `{kind: "no-result"}`). Both paths —
  notification (`handlePendingPermissionRequest`) and direct RPC
  (`permission.request`) — emit the correct kind string.
* `pending_permission_result_kind` updated to handle `NoResult`
  explicitly; `direct_permission_payload` updated to bypass
  ApproveOnce/Reject conversion for these new variants.

A.1 — disable_resume
* `ResumeSessionConfig::disable_resume: Option<bool>`. Mirrors Node's
  `ResumeSessionConfig.disableResume` and Go's `*bool DisableResume`.
  Forces resume to fail if the session is missing on disk rather than
  silently starting a new one.

Tests
* `list_sessions_serializes_typed_filter` — confirms the typed filter
  serializes camelCase, omits None fields, and dispatches via
  `session.list`.
* `mcp_server_config_roundtrips_through_tagged_enum` — confirms
  `type: "stdio"` serialize, `type: "local"` alias on deserialize, and
  HashMap roundtrip.
* `permission_request_data_extracts_typed_kind` — confirms typed kind
  extraction including the kebab-case `custom-tool` rename and the
  forward-compatible `Unknown` fallthrough.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document infinite_sessions parity + Client::stop deferral (Bucket A.2/A.6)

Notes-only CHANGELOG entries closing out the remaining Bucket A items:

* A.2 (infinite_sessions) — already wired on both SessionConfig and
  ResumeSessionConfig as `Option<InfiniteSessionConfig>` with
  default-omit-on-the-wire semantics. Matches Node/Go (CLI-applied
  defaults when the field is absent). No code change; document the
  parity decision so it doesn't get re-flagged.
* A.6 (Client::stop error aggregation) — defer behind a Client-level
  session registry. Real aggregation requires iterating Session handles
  to disconnect-then-kill, but the Rust Client only tracks per-session
  channel senders today (router::SessionSenders), not Session instances.
  A cosmetic Result<(), Vec<Error>> change with no behavior shift would
  be pure pass-through wrapping. Tracked for Bucket B alongside the
  registry refactor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Aggregate Client::stop errors across active sessions (Bucket B / A.6)

Rewrite `Client::stop` to cooperatively shut down every still-registered
session before terminating the CLI child:

  1. Snapshot active session IDs from the router (no lock held across
     the destroy RPCs).
  2. For each, send `session.destroy` and unregister.
  3. Take + terminate the child.
  4. Collect any errors from steps 2 and 3 into a new `StopErrors`
     aggregate and return `Result<(), StopErrors>`.

`StopErrors` is a public newtype around `Vec<Error>` implementing
`std::error::Error`; `errors()` and `into_errors()` expose the
underlying errors. Mirrors Node's `Error[]` shutdown return shape and
closes Bucket A.6 (deferred from the prior batch since true aggregation
required iterating active sessions, not just changing the signature).

Implementation detail: rather than introducing a parallel
`Weak<Session>` registry on the Client, this leans on the router's
existing session-ID HashMap. The router already tracks every session
the client has registered, so a `session_ids()` snapshot helper is the
only new state. No new Arc<Session> ceremony, no Drop-impl back-pointer,
no public API change to Session.

This is a breaking change to `Client::stop`'s return type. README's
quickstart example switches from `client.stop().await?` to
`client.stop().await.ok();` (best-effort shutdown) — callers that
care about per-session destroy errors can match on `StopErrors` and
inspect `errors()`.

Tests:
  - `client_stop_sends_session_destroy_for_each_active_session` — two
    registered sessions, both destroys observed on the wire, returns Ok.
  - `client_stop_aggregates_session_destroy_errors` — destroy returns
    a JSON-RPC error; `StopErrors` carries it and Display includes the
    underlying message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Bucket B.1 SessionConfig fields

Mirrors the six knobs the Node SDK exposes on `SessionConfig` /
`ResumeSessionConfig` that the Rust SDK was missing:

- session_id: Option<SessionId> on SessionConfig (custom session ID;
  remains required on ResumeSessionConfig as the existing field).
- working_directory: Option<PathBuf> (per-session cwd override,
  independent of ClientOptions::cwd).
- config_dir: Option<PathBuf> (override default config dir for this
  session).
- model_capabilities: Option<ModelCapabilitiesOverride> (per-property
  capability overrides, deep-merged at session create / resume time;
  the type was already used by SetModelOptions).
- github_token: Option<String> (per-session GitHub token, distinct
  from ClientOptions::github_token which authenticates the CLI process
  itself). Redacted from Debug output.
- include_sub_agent_streaming_events: Option<bool>.

Wire format: snake_case round-trips to camelCase via
#[serde(rename_all = "camelCase")] except gitHubToken, which uses an
explicit #[serde(rename = "gitHubToken")] to match Node's mixed-case
spelling.

Adds two regression tests in tests/session_test.rs verifying serde
output for the wire field names + Debug-redaction of the token.

CHANGELOG: documents the six new fields under "Configuration parity".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Bucket B.2 ClientOptions fields (log_level + idle timeout)

Mirrors two of the five Node CopilotClientOptions knobs the Rust SDK
was missing:

- log_level: Option<LogLevel> with a new typed enum (None / Error /
  Warning / Info / Debug / All), serialized lowercase to match the
  CLI's --log-level argument values. Replaces the previously hardcoded
  --log-level info in spawn_stdio / spawn_tcp; when unset the SDK still
  passes "info" for parity with prior behavior.
- session_idle_timeout_seconds: Option<u64>. When Some(n) with n > 0,
  the SDK passes --session-idle-timeout <n>. None or Some(0) leaves
  sessions running indefinitely (CLI default). Implementation lives
  in a new Client::session_idle_timeout_args helper, mirroring
  auth_args.

Adds four lib unit tests covering the helper plus LogLevel serde
round-trip.

Two of the five Node knobs are intentionally not ported and are
documented in the CHANGELOG as N/A:

- isChildProcess: requires a parent-stdio transport variant the Rust
  SDK does not yet support; tracked as a future addition rather than
  a Bucket B item.
- autoStart: does not apply to the Rust SDK's API shape — Client::start
  is a single explicit constructor rather than the deferred-init
  pattern Node uses.

The remaining onListModels (BYOK callback) is tracked separately;
its callback type design is non-trivial and warrants its own commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Bucket B.2 on_list_models BYOK callback override

Mirrors Node's `CopilotClientOptions.onListModels`: when set,
`Client::list_models` returns the handler's result without making
a `models.list` RPC. This is the BYOK escape hatch for environments
where the model catalog is provisioned separately from the CLI
(e.g. external inference servers selected via Transport::External).

API additions:
- `ListModelsHandler` async_trait with a single
  `async fn list_models(&self) -> Result<Vec<Model>, Error>` method.
  Mirrors the shape of `SessionHandler` / `SessionHooks` for
  consistency.
- `ClientOptions::on_list_models: Option<Arc<dyn ListModelsHandler>>`.
- `ClientOptions` switches from `#[derive(Debug)]` to a manual Debug
  impl that prints the handler as `<set>` / `None`. Same precedent
  as `SessionConfig::handler` and the redacted `github_token` field.

Plumbing:
- `ClientInner` gains an `on_list_models` field carrying the handler.
- `Client::from_transport` takes the handler as a new parameter;
  threaded through all 3 transport call sites in `Client::start`
  (External / Tcp / Stdio). `Client::from_streams` (no
  ClientOptions) passes None.
- `Client::list_models` consults the handler before falling back
  to the RPC.

Tests:
- `client_options_debug_redacts_handler`: confirms manual Debug
  prints `<set>` for the handler and continues redacting
  `github_token`.
- `list_models_uses_on_list_models_handler_when_set`: end-to-end
  override path with a counting handler.

Bucket B.2 is now complete: log_level, session_idle_timeout_seconds,
and on_list_models all ported. is_child_process and auto_start
remain documented as N/A (transport variant Rust doesn't have / API
shape mismatch).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add MessageOptions.request_headers (Phase 4 § 4.5)

Mirrors Node's `MessageOptions.requestHeaders` and Go's
`MessageOptions.RequestHeaders`: custom per-turn HTTP headers
forwarded to the CLI on the `session.send` request, used by the
CLI to inject headers into outbound model API calls for that turn.

API additions:
- `MessageOptions::request_headers: Option<HashMap<String, String>>`
  (already `#[non_exhaustive]` with builder pattern, so the new field
  is fully additive for existing callers).
- `MessageOptions::with_request_headers(headers)` chainable builder.

Wire format:
- `session.send` request payload gains a `requestHeaders` object
  when the field is `Some(map)` and the map is non-empty.
- Omitted entirely when `None` or empty — same `omitempty` semantics
  as Node's optional field (per Node types.ts:1521-1524).

Tests:
- `send_serializes_request_headers` — multi-header case verifies
  field name (camelCase `requestHeaders`) and value pass-through.
- `send_omits_request_headers_when_unset_or_empty` — covers both
  the unset (None) and empty-map cases, ensuring neither sends
  `"requestHeaders": {}` on the wire.

Bucket scope: this is the smallest of Phase 4's four 1.0-blocking
gaps. 4.1 (slash commands) and 4.2 (SessionFsProvider, ADR-worthy)
remain. 4.3 / 4.4 are pending team review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add slash command registration (Phase 4 § 4.1)

Introduces the consumer-facing surface for `/cmdname` slash commands,
matching Node's `SessionConfig.commands`, Python's, and Go's:

- `CommandHandler` async trait — single `on_command(ctx)` async method.
  Mirrors the SDK's existing `SessionHandler` / `ToolHandler` pattern
  (named trait, navigable in stack traces) over a boxed-closure shape.
- `CommandDefinition { name, description?, handler }` — `#[non_exhaustive]`
  with `new` + `with_description` builders. Manual `Serialize` emits only
  `{name, description?}` on the wire; handlers stay client-side. Manual
  `Debug` prints the handler as `<set>` (same precedent as
  `SessionConfig::handler`).
- `CommandContext { session_id, command, command_name, args }` —
  `#[non_exhaustive]`, what the handler receives.
- `SessionConfig::commands` and `ResumeSessionConfig::commands` —
  `Option<Vec<CommandDefinition>>` with `with_commands` builders. Field
  is `skip_deserializing` since `CommandDefinition` carries an opaque
  handler.

Wire-up:

- `Client::create_session` / `Client::resume_session` drain
  `config.commands` into an `Arc<HashMap<String, Arc<dyn CommandHandler>>>`
  via a new `build_command_handler_map` helper, threaded through
  `spawn_event_loop` to `handle_notification`.
- New `SessionEventType::CommandExecute` arm dispatches incoming
  `command.execute` notifications: looks up the handler by `commandName`,
  invokes it on a spawned task, then acks via
  `session.commands.handlePendingCommand` — no error on success,
  `error: <handler message>` on `Err`, and
  `error: "Unknown command: <name>"` when the name is unregistered
  (matches Node's behavior verbatim).

Tests (4 new in `rust/tests/session_test.rs`, all green):

- `create_serializes_commands_strips_handler` — wire payload contains
  only `{name, description?}`, no `handler` key.
- `command_execute_dispatches_to_registered_handler_and_acks_success`
  — handler invoked with right `CommandContext`, ack has no error.
- `command_execute_unknown_command_acks_with_error` — unregistered
  command name produces `Unknown command: <name>` ack.
- `command_execute_handler_error_propagates_to_ack` — handler `Err` is
  surfaced verbatim in the ack's `error` field.

Schema-side types (`CommandExecuteData`,
`SessionRpcCommands::handle_pending_command`, `RPC_METHOD_*`) were
already in `rust/src/generated/` — no codegen changes needed.

Cross-repo impact (additive): github-app's `SessionConfig { ... }`
literal at `core.rs:710` will need `commands: None,` added at sync time,
same mechanical pattern as Bucket B.1 fields.

CHANGELOG entry added under "Configuration parity".

Gates:
- cargo +nightly-2026-04-14 fmt --check ✅
- cargo clippy --all-features --all-targets -- -D warnings ✅
- cargo test --all-features ✅ (84 lib + 5 + 3 + 68 + 17 = 177 tests)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add ADR 0001: SessionFsProvider trait and plumbing (Phase 4 § 4.2)

Drafts the architecture decision record for the last 1.0 parity gap —
SessionFsProvider — before any implementation lands. Per the global
"ADRs for major decisions" convention and tclem's explicit guidance
on §4.2.

Establishes `rust/docs/adr/` as the home for Rust SDK ADRs (this is
the first one) with a short index in `rust/docs/adr/README.md`.

ADR scope:

- **Methodology** — explicit verify-before-drafting audit, citing the
  consistent wins from A.2 / A.6 / 4.5 / 4.1. Captures the cross-SDK
  source map (Node + Python + Go + generated Rust types) so reviewers
  can verify the proposed shape against actual upstream code.
- **Trait shape** — async_trait with 10 methods mirroring Node's
  provider, returning `Result<T, FsError>`. Sync alternative and
  trait-erased boxed-closure alternative both rejected with rationale.
- **Method signatures** — Rust-idiomatic `Result<T, FsError>` with the
  SDK adapting to the schema's `{ ..., error: Option<SessionFsError> }`
  payload. `FsError::NotFound` → `ENOENT`, `FsError::Other` → `UNKNOWN`.
  `From<io::Error>` provided so `tokio::fs`-backed handlers can `?`.
- **Concurrency model** — concurrent dispatch (each `sessionFs.*`
  request on its own spawned task), `Send + Sync` providers must be
  re-entrant. Per-session sequential dispatch rejected with rationale
  (CLI parallelism assumption).
- **Plumbing** — direct `Arc<dyn SessionFsProvider>` registration on
  SessionConfig, NOT a factory closure like Node/Python/Go. Sidesteps
  the "lambdas as fn args" rule, idiomatic Rust, callers can carry
  session-id refs themselves. Trade-off documented in "Differences
  from other SDKs" call-out planned for the README. Future
  factory-closure form can land additively if needed.
- **Inbound dispatch** — new arms in `handle_request` for all 10
  `sessionFs.*` methods, dispatched through a per-session
  `Arc<HashMap<SessionId, Arc<dyn SessionFsProvider>>>` map mirroring
  the §4.1 `command_handlers` shape.
- **Naming + module layout** — `rust/src/session_fs.rs` for public
  surface, `rust/src/session_fs_dispatch.rs` (`pub(crate)`) for the
  request adapters. `SessionFsConventions { Posix, Windows }` is
  hand-authored to avoid the generated enum's catch-all `Unknown`
  variant on the consumer-input side.
- **Forward compat** — `#[non_exhaustive]` on `SessionFsConfig`,
  `FsError`, `FileInfo`, `DirEntry`, `DirEntryKind`. Future trait
  methods land with default impls returning `Err(FsError::Other(...))`
  so existing impls continue to compile.
- **Tauri-app non-impact** — audited; `src-tauri/` does not register
  a virtual filesystem provider today. `ClientOptions { ... }` literal
  in `cli.rs` will need `session_fs: None,` added at sync time, same
  mechanical pattern as Bucket B.2.
- **Implementation order** — 11-step ordered plan, from trait
  authoring through CHANGELOG entry, so the actual implementation
  commit can follow ADR approval without sequencing surprises.

This commit ships the ADR only — no implementation code, no public
API changes, no test changes. The ADR is `Status: Proposed` until
tclem and the Rust SDK working group sign off.

ASCII-only diagrams per repo style. No external markdown rendering
dependencies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* rust: implement SessionFsProvider (Phase 4 § 4.2)

Adds a virtualizable filesystem provider that lets host applications
sandbox sessions, project files into in-memory or remote storage, and
apply permission policies before bytes move. Last 1.0-blocking parity
gap before public release.

Public API additions (in `crate::session_fs`, re-exported from
`crate::types`):

- `SessionFsProvider` async trait with 10 methods (`read_file`,
  `write_file`, `append_file`, `exists`, `stat`, `mkdir`, `readdir`,
  `readdir_with_types`, `rm`, `rename`). All methods have default impls
  returning `Err(FsError::Other("not supported"))` so providers only
  override what they need and forward-compatible schema additions land
  without breaking implementors.
- `SessionFsConfig` (initial_cwd, session_state_path, conventions),
  `SessionFsConventions` (Posix/Windows), `FsError` (NotFound/Other),
  `FileInfo`, `DirEntry`, `DirEntryKind`. All `#[non_exhaustive]`.
- `ClientOptions::session_fs: Option<SessionFsConfig>` — when set,
  `Client::start` calls `sessionFs.setProvider` after protocol-version
  verification.
- `SessionConfig::with_session_fs_provider` /
  `ResumeSessionConfig::with_session_fs_provider` builders for
  registering an `Arc<dyn SessionFsProvider>` per session.
- `From<std::io::Error>` on `FsError` (NotFound→NotFound, anything
  else→Other) so handlers backed by `std::fs` / `tokio::fs` can use `?`.

Wire dispatch:

- `pub(crate) crate::session_fs_dispatch` module bridges the trait to
  the schema. `FsError::NotFound` maps to wire `ENOENT`; all other
  errors map to `UNKNOWN` with the message preserved for diagnostics.
- The session event loop forwards inbound `sessionFs.*` requests to
  the dispatch module; non-fs methods continue to the existing
  `unknown method` error path.
- New `SessionError::SessionFsProviderRequired` and
  `SessionError::InvalidSessionFsConfig` variants surface
  configuration errors at `Client::start` / `create_session` time.

Divergence from Node/Python/Go:

- This SDK accepts `Arc<dyn SessionFsProvider>` directly, rather than a
  factory closure that builds a provider per session. There is no
  `Session` value to thread into a factory at config time, and the SDK
  already prefers traits over boxed closures for handler-shaped APIs
  (`SessionHandler`, `SessionHooks`, `ToolHandler`). See
  `rust/docs/adr/0001-session-fs-provider.md` for the rejected-factory
  rationale and a forward-compat escape hatch
  (`with_session_fs_provider_factory`) that can be added additively
  post-1.0 if a real factory use case emerges. ADR status flipped from
  Proposed to Accepted in this commit.

Tests:

- 7 mock-server tests in `rust/tests/session_test.rs` covering
  read_file dispatch, NotFound→ENOENT mapping, Other→UNKNOWN mapping,
  write_file with mode, readdir_with_types, rm with force, and
  validation rejecting empty `initial_cwd`.
- Inline unit tests in `session_fs.rs` for the io::Error→FsError
  conversion.

Docs / examples:

- `rust/examples/session_fs.rs` — in-memory provider example.
- `rust/CHANGELOG.md` — entry under "Configuration parity".
- `rust/README.md` — new "Differences From Other SDKs" section
  linking to the ADR.

Verified:

- `cargo +nightly-2026-04-14 fmt --check`
- `cargo clippy --all-features --all-targets -- -D warnings`
- `cargo test --all-features` (190 tests pass; 3 ignored require CLI)

Cross-repo impact (additive only):

- `src-tauri/src/session/cli.rs:337` `ClientOptions { ... }` literal —
  adds `session_fs: None`.
- `src-tauri/src/session/core.rs:710` `SessionConfig { ... }` literal —
  adds `session_fs_provider: None`.
- `src-tauri/src/session/core.rs:749` `ResumeSessionConfig { ... }`
  literal — adds `session_fs_provider: None`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add W3C Trace Context propagation (Phase 4 § 4.3)

Implements the cross-SDK 1.0-blocking `traceparent` / `tracestate` plumbing
across the three RPC injection points and the inbound tool-invocation read
path. Hybrid shape combines Node's callback ergonomics with Go's per-turn
override:

- New `TraceContext` struct (`#[non_exhaustive]`) and `TraceContextProvider`
  async trait in `crate::trace_context`, re-exported from `crate::types`.
- `ClientOptions::on_get_trace_context: Option<Arc<dyn TraceContextProvider>>`
  supplies an ambient provider invoked on `session.create`, `session.resume`,
  and `session.send`. Manual `Debug` impl prints `<set>` / `None` matching
  the `on_list_models` precedent.
- `MessageOptions` gains `traceparent` / `tracestate: Option<String>` plus
  three builders — `with_trace_context`, `with_traceparent`,
  `with_tracestate`. Per-turn values override the callback (provider is not
  invoked when MessageOptions already carries trace headers).
- `ToolInvocation` flipped to `#[non_exhaustive]` and exposes inbound
  `traceparent` / `tracestate` populated from `external_tool.requested`
  events, plus a `trace_context()` helper. Wire fields are omitted when
  unset, matching the schema's `omitempty` semantics.
- New test-only constructor `Client::from_streams_with_trace_provider`
  (gated on `cfg(test)` / `feature = "test-support"`) so integration tests
  can exercise the callback path.

Tests: 7 new mock-server scenarios in `tests/session_test.rs` covering
provider invocation on create/resume/send, MessageOptions-overrides-callback,
MessageOptions-without-callback, ToolInvocation read path, and wire-omits-
when-unset. Full suite: 88 lib + 83 integration green; clippy and nightly
fmt clean.

No formal ADR — `ListModelsHandler` and `MessageOptions` precedents already
cover both shapes. CHANGELOG entry under "Configuration parity" documents
the cross-SDK rationale.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Implement Default on ToolInvocation for test ergonomics

Adds `#[derive(Default)]` to `ToolInvocation` (and to `SessionId`, which
the derive depends on). Pure additive: production code never constructs
`ToolInvocation` literals (it's a CLI-emitted read-only type), so the
"meaningless empty defaults" cost is genuinely zero in practice.

The win is downstream test scaffolding. With `..Default::default()`
available, test sites that build `ToolInvocation` literals to drive
handler tests can collapse boilerplate and absorb future
`#[non_exhaustive]` field additions automatically — no need to
re-touch every test scaffold every time the schema grows. Aligns with
the rust-coding-skill's "use `..Default::default()` in tests to reduce
boilerplate when adding fields" guidance, and mirrors `MessageOptions`
which already derives `Default`.

`SessionId` is a transparent newtype around `String`, so its `Default`
is `SessionId(String::new())` — already the natural identity for an
unset session ID.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add TelemetryConfig env-var passthrough on ClientOptions (Phase 4 § 4.4)

Closes the last 1.0-blocking gap in Phase 4 of the Rust SDK release prep.
Adds opt-in OpenTelemetry configuration mirroring Node/Python/Go.

## Surface

- `TelemetryConfig` (`#[non_exhaustive]`, `Debug + Clone + Default`) with
  five `Option`-typed fields: `otlp_endpoint`, `file_path`,
  `exporter_type`, `source_name`, `capture_content`. No `Serialize` /
  `Deserialize` derives — this type is not wire-serialized; it's pure
  spawn-time env-var injection.
- `OtelExporterType` enum (`#[non_exhaustive]`, `Serialize`/`Deserialize`
  with `rename_all = "kebab-case"`): `OtlpHttp`, `File`. Typed rather than
  `Option<String>` to match the `LogLevel` precedent (B.2) for finite,
  enumerated CLI knobs.
- `ClientOptions::telemetry: Option<TelemetryConfig>`, defaulting to
  `None`. Field added to the manual `Debug` impl.

## Wire behavior

When `ClientOptions::telemetry` is `Some(...)`, `Client::build_command`
sets `COPILOT_OTEL_ENABLED=true` plus, for each populated field:

- `otlp_endpoint`         -> `OTEL_EXPORTER_OTLP_ENDPOINT`
- `file_path`             -> `COPILOT_OTEL_FILE_EXPORTER_PATH`
- `exporter_type`         -> `COPILOT_OTEL_EXPORTER_TYPE` (`"otlp-http"` / `"file"`)
- `source_name`           -> `COPILOT_OTEL_SOURCE_NAME`
- `capture_content`       -> `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`
                             (`"true"` / `"false"`)

Env-var names are byte-for-byte identical to the Node/Python/Go SDKs.
`capture_content` serializes as lowercase `"true"`/`"false"` matching
Node's `String(t.captureContent)` and Python's `str(...).lower()`.

## Env-var precedence

`build_command` env order: auth token -> telemetry -> `options.env`
(user override) -> `options.env_remove`. User-supplied env always wins
over both auth and telemetry, by ordering. Tests cover this.

## Design notes

- Pure env-var passthrough: zero new dependencies. The CLI itself owns
  the OpenTelemetry exporter; the SDK is just a config conduit. All
  three reference SDKs (Node, Python, Go) work this way — verified
  before drafting (verify-before-drafting tally now at 8 wins).
- No ADR. Plan-doc explicitly endorsed skipping the ADR since this
  isn't an architectural decision: the shape is fully determined by
  cross-SDK precedent. ADR directory is reserved for non-obvious
  choices; precedent verification belongs in CHANGELOG + commit body.
- `#[non_exhaustive]` on both `TelemetryConfig` and `OtelExporterType`
  to absorb future CLI-side telemetry knobs (sampling, additional
  exporters) without breaking changes.
- `PathBuf` (not `String`) for `file_path` for type safety;
  `Command::env` accepts `AsRef<OsStr>` so no conversion needed.

## Tests

Five new tests in `lib.rs`:

- `build_command_sets_otel_env_when_telemetry_enabled` — full
  TelemetryConfig set; asserts all six expected env vars propagate with
  correct values.
- `build_command_omits_otel_env_when_telemetry_none` — default
  `ClientOptions`; asserts none of the six env vars are present.
- `build_command_omits_unset_telemetry_fields` — only `otlp_endpoint`
  set; asserts that single field plus the implicit enabled flag are
  set, and the four others are absent.
- `build_command_lets_user_env_override_telemetry` — telemetry sets
  `OTEL_EXPORTER_OTLP_ENDPOINT="http://from-config:..."`, user env
  sets it to `"http://from-user-env:..."`; asserts user env wins.
- `telemetry_config_capture_content_serializes_as_lowercase_bool` —
  asserts both `Some(true)` and `Some(false)` serialize as the bare
  lowercase boolean strings.

All tests pass; `cargo fmt --check`, `cargo clippy -- -D warnings`,
`cargo test --all-features` all green.

## Cross-repo impact

Existing `ClientOptions { ... }` literal in github-app `cli.rs:337`
gains one new field: `telemetry: None`. No other consumer-side
changes. The Tauri app's existing telemetry pipeline is for the host
app and doesn't interact with the SDK config — this is purely about
forwarding telemetry env vars to the spawned CLI child.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document Rust-only API surface (Phase 4 § 4.7)

Doc-only follow-up to the Phase 4 parity work. Two pieces of content,
no API changes.

## Changes

1. **`Client::get_quota` rustdoc** — flags the method as Rust-only as
   of 0.1.0. The underlying `account.getQuota` JSON-RPC endpoint is
   exposed only by the Rust SDK in this release; Node, Python, Go, and
   .NET don't surface it. Brief, factual, no backport-issue links.

2. **"Rust-only API" subsection in `rust/README.md`** — restructures
   the existing "Differences From Other SDKs" section into two
   subsections: "Shape divergence" (the existing `SessionFsProvider`
   factory-vs-direct discussion) and a new "Rust-only API" listing the
   surface that exists only on the Rust SDK as of 0.1.0:

   - `Client::get_quota`
   - First-class `Session` convenience methods (`set_mode`/`get_mode`,
     `set_name`/`get_name`, `read_plan`/`update_plan`/`delete_plan`,
     `start_fleet`, `list_workspace_files`/`read_workspace_file`/
     `create_workspace_file`)
   - Typed newtypes (`SessionId`, `RequestId`)
   - Permission policy builders (`permission::approve_all`,
     `permission::deny_all`, `permission::approve_if`)
   - `Client::from_streams` (arbitrary `AsyncRead`/`AsyncWrite`)
   - `enum Transport { Stdio, Tcp, External }`
   - Split `prefix_args` / `extra_args`

   Tone is "Rust gets to be Rust" — not apologetic, no backport
   promises. Cross-SDK parity for these is explicitly framed as a
   post-release conversation, not a release blocker.

## Bonus: drive-by intra-doc link fix

Fixed a pre-existing broken intra-doc link in `session_fs.rs` (`[`tokio::fs`]`
-> explicit URL link). Caught by `cargo doc --no-deps --all-features`,
which now builds clean. Doing it here rather than as a separate commit
since the testing-pass that comes next is going to require clean docs
output anyway, and the fix is one line.

`cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo test --doc`,
and `cargo doc --no-deps --all-features` all green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Broaden skills discovery wording in copilot-instructions.md

Skills under .github/skills/ are auto-discovered by Copilot tooling
broadly (CLI, Copilot Coding Agent, etc.), not just Copilot CLI.
Per stephentoub's review on PR #1164.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix ConnectionState::Errored wire form to match Go ("error" not "errored")

Caught by stephentoub on PR #1164. ConnectionState's serde
`rename_all = "lowercase"` was producing "errored" for the Errored
variant, but Go's source-of-truth uses "error". Add explicit
`#[serde(rename = "error")]` on the variant so the wire form matches
Go's ConnectionState string. Variant name stays `Errored` to avoid
shadowing the std `Error` trait and the crate's own `Error` type.

Adds two unit tests locking in the wire format for all four variants.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename ConnectionState::Errored to ConnectionState::Error

Per follow-up on stephentoub's review of PR #1164: prefer renaming the
variant to drop the `#[serde(rename = "error")]` attribute. The variant
is unused outside types.rs (the Client transitions Disconnected /
Connecting / Connected today; Error is reserved for future use), so
renaming has no consumer impact and produces a cleaner enum surface.

`ConnectionState::Error` does not collide with anything in scope:
`types.rs` does not import `crate::Error` (it uses fully-qualified
`crate::Error` at use sites) and `std::error::Error` is unimported. The
variant lives inside a typed enum, so no shadowing concern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR #1164 cross-SDK consistency review

Three review-feedback fixes folded into a single commit:

- `session.ui()` sub-API: move `elicitation`, `confirm`, `select`,
  `input` onto a new `SessionUi<'a>` view returned by `Session::ui()`.
  Mirrors .NET `session.UI`, Python `session.ui`, Go `session.UI()`.
  Wire-method names unchanged. Per stephentoub on
  https://github.com/github/copilot-sdk/pull/1164#discussion_r3161986035
  and the bot duplicate at #discussion_r3163241529.

- Typed `Client::get_status` and `Client::get_auth_status` returns:
  introduce `GetStatusResponse { version, protocol_version }` and
  `GetAuthStatusResponse { is_authenticated, auth_type, host, login,
  status_message }`, both `#[non_exhaustive]`. Matches Node / Go /
  Python typed shapes. Per the bot at
  https://github.com/github/copilot-sdk/pull/1164#discussion_r3162629019.

- `Session::set_model` now returns `Result<(), Error>` instead of
  `Result<Option<String>, Error>`. Mirrors Node / Python / Go / .NET
  void-on-success semantics. Per the bot at
  https://github.com/github/copilot-sdk/pull/1164#discussion_r3162816817.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Type MessageOptions::mode as DeliveryMode enum

Promote MessageOptions::mode from Option<String> to Option<DeliveryMode>,
where DeliveryMode is a #[non_exhaustive] enum with Enqueue (default) and
Immediate variants serializing to "enqueue" / "immediate". Mirrors Node
types.ts:1519 (mode?: "enqueue" | "immediate") and Go types.go:858
(MessageOptions.Mode is the message delivery mode (default: "enqueue")).

The prior rustdoc on MessageOptions::mode incorrectly described the field
as a permission mode. The field has always controlled message delivery
relative to in-flight session work; the doc lied. Strings other than
"enqueue" / "immediate" were silently no-ops at the CLI, so the typed enum
turns wrong-string-at-call-site from a runtime nothing-happens into a
compile error.

Precedent for typed enums on enumerated CLI knobs: B.2 LogLevel
(c4132c2), 4.4 OtelExporterType (aefb108), SessionFsConventions, and
DirEntryKind. The asymmetric request_headers (4.5) stayed
HashMap<String, String> because that value space is open; mode is finite
and CLI-controlled.

with_mode signature shrinks from impl Into<String> to DeliveryMode and
the wire injection in session.rs uses serde_json::to_value(m). Added a
roundtrip test in types.rs covering both variants.

CHANGELOG entry under Configuration parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Default permission-flow flags to Some(true)

SessionConfig::default() and ResumeSessionConfig::new() now set the four
permission-flow flags to Some(true):

  - request_user_input
  - request_permission
  - request_exit_plan_mode
  - request_elicitation

Mirrors Node's client.ts:749-751 which always sets requestPermission: true
and derives requestUserInput/requestElicitation from handler presence. In
Rust, SessionHandler is trait-based so a handler is always installed
(DenyAllHandler is the default) — opt-in defaults of None meant a Rust
caller could install a SessionHandler and forget to flip the flags,
silently breaking permission flow vs. the equivalent Node code.

The default DenyAllHandler refuses all permission requests so the wire
surface is safe out-of-the-box. Callers that want the wire surface fully
disabled set the flags explicitly to Some(false).

SessionConfig drops its derived Default in favor of a manual impl that
enumerates every field. ResumeSessionConfig::new() switches the four
field assignments from None to Some(true). Field rustdoc updated on each
of the four fields to call out the default. Two unit tests added in
types.rs covering both constructors.

CHANGELOG entry under Configuration parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Mark remaining public config types non_exhaustive

Adds #[non_exhaustive] to the 10 remaining public configuration types
that didn't already carry the attribute:

  - SessionConfig
  - ResumeSessionConfig
  - ClientOptions
  - ProviderConfig
  - McpServerConfig
  - Tool
  - CustomAgentConfig
  - InfiniteSessionConfig
  - SystemMessageConfig
  - ConnectionState

HookEvent, HookOutput, MessageOptions, TelemetryConfig, SessionFsConfig,
FsError, FileInfo, DirEntry, ToolInvocation, Error, Transport, and the
new DeliveryMode were already marked. Closing the asymmetry now means
adding fields to any of these post-1.0 is non-breaking on consumers that
construct via Default::default() plus field assignment or the with_*
builders.

Tradeoff: external crates can no longer use struct-literal syntax for
these types -- not even with ..Default::default(), which only works
inside the defining crate. Tests, examples, and the tool_parameters
doctest are migrated to the let-mut + field-assignment pattern. Callers
porting from 0.1.0-* will see the same compile error and apply the same
mechanical transform.

CHANGELOG entry under Configuration parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix InputOptions doc-link to SessionUi::input

The UI-grouping commit f4aa8d9 moved input/select/confirm/elicitation off
Session into SessionUi, but the rustdoc on InputOptions still referenced
the old crate::session::Session::input path. Cargo doc with
-D rustdoc::broken_intra_doc_links rejected the link.

Repoints the link to crate::session::SessionUi::input.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Drop cross-SDK comparisons from Rust source comments

Per-symbol "Mirrors Node's Foo / Go's Bar / Python's Baz" rustdoc is
unscalable and drifts as the other SDKs evolve. The Rust SDK seeks parity
with Node/Python/Go/.NET; that fact is now stated once at the top of
rust/README.md, and intentional divergences live in the README's
"Differences From Other SDKs" section.

- Strip "Mirrors X" / "Unlike Y" / cross-SDK file:line citations from
  rustdoc across lib.rs, session.rs, types.rs, session_fs.rs,
  trace_context.rs. Replace with API-shape descriptions where the dropped
  text carried real information (e.g. wire-string variants).
- README: add a one-line parity statement up top that points to the
  existing Differences section.
- Update .github/skills/rust-coding-skill/SKILL.md to forbid cross-SDK
  references in code comments and rustdoc going forward, with explicit
  guidance that intra-SDK self-references ("Mirrors `from_streams`") are
  still fine.

cargo doc -D warnings clean. cargo clippy clean. cargo fmt clean.
18 doctests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Move SessionFs ADR out of public crate

ADR 0001 (SessionFsProvider trait and plumbing) captured the design
rationale for choosing direct Arc<dyn SessionFsProvider> registration
over the factory-closure pattern that Node/Python/Go use. That
rationale is internal release-engineering history, not consumer-facing
API documentation; the README's "Differences From Other SDKs" section
already explains what consumers need to know about the divergence.

The full ADR is being relocated to the private SDK release-plan
documentation in github/github-app PR #3166 (docs/copilot/2026-04-14-sdk-release/).

- Delete rust/docs/ entirely (only contained ADR 0001 + index README).
- Strip ADR links from rustdoc, README, CHANGELOG, and the
  session_fs example. The README's existing inline rationale (factory
  pattern doesn't cleanly express in Rust at the session-config call
  site, no `Session` value to thread in, SDK already prefers traits
  over closures for handler-shaped APIs) is sufficient on its own.

cargo doc -D warnings clean. cargo clippy clean. cargo fmt clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix SessionUi::elicitation wire field name

The hand-authored `SessionUi::elicitation` convenience layer was
sending the JSON Schema payload as `"schema"` on the wire, but the
`session.ui.elicitation` request shape expects `"requestedSchema"`.
This is the field name in:

- this crate's own generated UIElicitationRequest type
  (rust/src/generated/api_types.rs:1721, serde renames to
  `requestedSchema` via #[serde(rename_all = "camelCase")])
- the generated typed RPC wrapper SessionRpcUi::elicitation
  (rust/src/generated/rpc.rs:1245-1257), which is correct
- and the same wire field used by every other SDK we ship

So every elicitation call from the SessionUi convenience layer was
effectively dead — the CLI saw a missing required `requestedSchema`
field. The `confirm` / `select` / `input` helpers all delegate to
`elicitation`, so they were dead too.

The mock-server test for elicitation round-tripped through the same
misnamed field on both ends, so the bug slipped past unit tests
(`assert_eq!(request["params"]["schema"], schema)` matched the buggy
implementation). The fix is a one-line rename in session.rs plus a
test update that now asserts on `requestedSchema` and explicitly
rejects a stray `schema` key, so we can't regress the same way.

207 tests pass. doc / clippy / fmt clean.

Caught by the gap-analysis re-run before 0.1.0 cut. Wire-shape
divergence between hand-authored and generated layers — same class
of bug as the workspace RPC fix earlier in this stack.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add typed on_auto_mode_switch handler for rate-limit recovery

Wires the CLI's `autoModeSwitch.request` JSON-RPC callback (added by
copilot-agent-runtime PR #7024 — github/copilot-agent-runtime#7024) into
the SDK's typed handler surface. When an eligible rate limit is hit,
the runtime asks the SDK whether to switch the session to auto model;
this commit gives consumers a typed entry point for that prompt
matching the shape of the existing exit-plan-mode / user-input /
elicitation handshakes.

Background: the github-app's vendored copilot-sdk crate has carried a
private version of this handler since Apr 27 (github-app commit
02ec73588). The plumbing was never upstreamed because no other SDK had
a typed handler for it, and the wire-protocol gap on the runtime side
hadn't yet closed. With #7024 merged, the wire path is well-defined
(`autoModeSwitch.request` → `{ response: "yes" | "yes_always" | "no" }`)
and the typed handler can ship.

Cross-SDK divergence: typed handler exists only in the Rust SDK as of
0.1.0. Node, Python, Go, and .NET observe the request as a raw
JSON-RPC callback today; parity ports for those SDKs are post-release
follow-up work and are noted in the README's "Rust-only API" section
and in the changelog.

Wire shape and types:

- `handler::AutoModeSwitchResponse` — typed enum (`Yes`, `YesAlways`,
  `No`) with `#[serde(rename_all = "snake_case")]`. Wire values are
  byte-identical to the runtime's schema. `#[non_exhaustive]` so future
  variants are additive. Ships as a typed enum rather than the
  `{ response: String }` shape used in the github-app vendored copy —
  consistent with the recently-landed `DeliveryMode` enum and the
  general convention that finite, enumerated wire values get typed at
  the API surface (LogLevel, OtelExporterType, ConnectionState,
  PermissionRequestKind, etc.).
- `handler::HandlerEvent::AutoModeSwitch { session_id, error_code,
  retry_after_seconds }` — new event variant. `error_code` is the
  rate-limit kind (e.g. `user_weekly_rate_limited`). `retry_after_seconds`
  is the RFC 9110 `Retry-After` `delta-seconds` value when the runtime
  knows it; consumers can surface a humanized reset time alongside the
  prompt.
- `handler::HandlerResponse::AutoModeSwitch(AutoModeSwitchResponse)` —
  new response variant.
- `SessionHandler::on_auto_mode_switch` — new trait method with safe
  default (`No`). Default `on_event` dispatches to it.
- `types::SessionConfig::request_auto_mode_switch: Option<bool>` and
  `types::ResumeSessionConfig::request_auto_mode_switch: Option<bool>`
  — new opt-in flags, both default to `Some(true)` via
  `SessionConfig::default()` and `ResumeSessionConfig::new()` to match
  the convention already established by the other four `request_*`
  flags. Without the flag the runtime doesn't dispatch the callback.
- `session.rs` `handle_request` — new arm for `autoModeSwitch.request`
  alongside the existing `permission.request` / `userInput.request` /
  `exitPlanMode.request` cases. Extracts `errorCode` /
  `retryAfterSeconds` from params, dispatches via
  `handler.on_event(HandlerEvent::AutoModeSwitch { ... })`, serializes
  the typed response back as `{ "response": "yes" | "yes_always" |
  "no" }`. Falls through to `No` if the handler returns an unexpected
  response variant.

Tests:

- `auto_mode_switch_dispatches_to_handler_and_serializes_response` —
  asserts the inbound `autoModeSwitch.request` reaches the typed
  handler with the expected `error_code` / `retry_after_seconds`, and
  that returning `AutoModeSwitchResponse::YesAlways` serializes as
  `{ "response": "yes_always" }` on the wire.
- `auto_mode_switch_default_handler_replies_no` — asserts the trait's
  default impl replies `{ "response": "no" }` when no override is
  provided.
- Updated `session_config_default_enables_permission_flow_flags` and
  `resume_session_config_new_enables_permission_flow_flags` to assert
  the new flag is `Some(true)` by default.

Documentation:

- README: new bullet under "Rust-only API" section.
- CHANGELOG: new entry under `### Added > Handlers + helpers`
  documenting the wire path, the typed enum, and the cross-SDK
  divergence with a note that Node / Python / Go / .NET parity is
  post-release work.
- `request_auto_mode_switch` field rustdoc cross-references
  `SessionHandler::on_auto_mode_switch`.

Validation:

- 209 tests pass (was 207 — 2 new auto-mode-switch tests).
- `cargo doc -D warnings` clean.
- `cargo +nightly-2026-04-14 fmt --check` clean.
- `cargo clippy --all-features --all-targets -- -D warnings` clean.

Migration note for the github-app sync session: the typed
`AutoModeSwitchResponse` enum replaces github-app's vendored
`AutoModeSwitchResponse { response: String }`. github-app's WebSocket
relay continues to receive the user's choice as a `String`; map to the
typed enum at the boundary
(`match s.as_str() { "yes" => Yes, "yes_always" => YesAlways, _ => No }`).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Client::list_sessions wire shape — wrap filter under params.filter

The hand-authored `Client::list_sessions` was serializing the optional
`SessionListFilter` directly onto the JSON-RPC `params` object,
flattening fields like `repository` / `branch` / `cwd` / `gitRoot` to
the top level. The `session.list` request shape that the runtime accepts
puts the filter under `params.filter` — and that's what every other SDK
sends:

- Node `nodejs/src/client.ts:1178-1180`: `sendRequest("session.list", { filter })`
- Go `go/types.go`: `listSessionsRequest { Filter *SessionListFilter }`
- Python `python/copilot/client.py:1907-1911`: `payload["filter"] = ...`
- .NET `dotnet/src/Client.cs`: `record ListSessionsRequest(SessionListFilter? Filter)`

Because the runtime silently ignores unknown top-level keys on
`session.list`, calling `list_sessions(Some(filter))` was functionally
equivalent to `list_sessions(None)` in 0.0.x — every filter field was
discarded by the runtime, returning an unfiltered session list. No
runtime error, no log, just silently broken. Functionally dead on the
wire, same class as the elicitation `requestedSchema` fix in `c58e2f2`.

The mock-server test `list_sessions_serializes_typed_filter` asserted on
the flat shape it observed (`request["params"]["repository"]`) rather
than the schema's wrapped shape, so the bug round-tripped through both
ends — the implementation produced the wrong shape, the test verified
the wrong shape. Same root cause as the elicitation test gap.

Fix:

- `Client::list_sessions` now wraps the filter: `Some(f) ->
  serde_json::json!({ "filter": f })`, `None -> serde_json::json!({})`.
  `None` omits the filter key entirely (matches Go's `omitempty`
  behavior; Node's `{ filter: undefined }` also omits via JSON-stringify).
- Mock-server test now asserts on the wrapped path
  (`params.filter.repository`, `params.filter.branch`) AND explicitly
  asserts the flattened fallback is gone (`params.get("repository")`
  must return `None`). Same regression-prevention pattern as the
  elicitation fix at `session_test.rs:1248-1251`.
- CHANGELOG entry under `### Fixed` documenting the wire-shape fix and
  the test gap that masked it.

Validation:

- 209 tests pass (no count change — same test, stricter assertions).
- `cargo doc -D warnings` clean.
- `cargo +nightly-2026-04-14 fmt --check` clean.
- `cargo clippy --all-features --all-targets -- -D warnings` clean.

Caught by the gap-analysis structural-correctness pass walking every
hand-authored `client.call("...")` site against the schema and the four
other SDKs. This is the second wire-shape bug found by that pass; the
first was the `SessionUi::elicitation` `schema` -> `requestedSchema`
fix in `c58e2f2`. The audit confirms `session.list` is the only other
new bug — three Rust-unique surfaces (`session.respondToUserInput`,
`session.sendTelemetry`, top-level `sendTelemetry` /
`server.sendTelemetry`) are uncheckable cross-SDK and queued as
post-0.1.0 runtime-acceptance verification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Bump @github/copilot pin to ^1.0.39 + regen Rust types

Per the auto-mode-switch CLI hunt session's verification, the
`@github/copilot@1.0.39-0` pin we were on does NOT contain
copilot-agent-runtime PR #7024 (auto-mode-switch wire support).
The runtime PR merged after `1.0.39-0` was cut as a release-candidate
and shipped in `1.0.39` final, with `1.0.40-0` after.

This commit:

- Bumps `nodejs/package.json` from `^1.0.39-0` to `^1.0.39` (final).
  `npm install` resolved to `1.0.39`. The package-lock.json is
  regenerated accordingly.
- Regenerates Rust t…
edburns pushed a commit that referenced this pull request Aug 4, 2026
* Add Rust SDK

Adds the Copilot Rust SDK (`copilot-sdk` crate) under `rust/`,
alongside Rust codegen plumbed into `scripts/codegen/` and CI under
`.github/workflows/rust-sdk-tests.yml`. The crate ships a JSON-RPC
client, session lifecycle management, system message transforms,
permission policy helpers, the `define_tool` adapter, and per-event
`SessionHandler`/`SessionHooks` traits.

Includes:

- 14 ported E2E scenarios under `rust/tests/` driving the replay-proxy
  harness, plus a hand-curated set of unit tests.
- A rust-coding-skill (`.github/skills/rust-coding-skill/`) capturing
  conventions for error handling, async/concurrency, tracing, and the
  intentional trait exceptions in the SDK's public API.
- Release tooling: `rust-publish-release.yml`, `RELEASING.md`, and
  protocol-version generation wired into the existing automation.
- `PermissionResult` extended with `Deferred` and `Custom` variants
  for richer permission decisions.

Public API is held at 0.1.0-pre. Marked protocol-evolving public enums
`#[non_exhaustive]` so additive variants stay non-breaking.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Christopher Schleiden <cschleiden@github.com>
Co-authored-by: David Dossett <25163139+daviddossett@users.noreply.github.com>
Co-authored-by: Devraj Mehta <devm33@github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Co-authored-by: Evan Boyle <EvanBoyle@users.noreply.github.com>
Co-authored-by: Jeremy Moseley <jemoseley@microsoft.com>
Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>

* Polish public API for 0.1.0 release

- **Broadcast subscriptions for lifecycle and session events.**
  `Client::subscribe_lifecycle()` and `Session::subscribe()` return
  `tokio::sync::broadcast::Receiver`; dropping the receiver
  unsubscribes. Replaces the prior callback-based `Client::on`,
  `Client::on_event_type`, `Session::on`, and `Unsubscribe` API.
  Spawned consumer tasks isolate panics naturally.
- **`PermissionResult` gains `Deferred` and `Custom` variants.**
  `Deferred` lets handlers resolve a request asynchronously via
  `session.permissions.handlePendingPermissionRequest` (notification
  path only — falls back to `Approved` on the direct RPC path).
  `Custom(Value)` lets handlers send arbitrary response payloads
  beyond the standard `approve-once` / `reject` shapes.
- **`#[non_exhaustive]` on protocol-evolving public enums**
  (`PermissionResult`, `SessionLifecycleEventType`,
  `GitHubReferenceType`, others) so additive variants stay
  non-breaking.
- **`ToolHandlerRouter` overrides per-event `SessionHandler` methods**
  so consumers can call `router.on_external_tool(...)` directly
  without unwrapping `HandlerResponse`.
- **`define_tool` accepts bare `async fn` items** in addition to
  closures, matching `tower::service_fn` /
  `hyper::service::service_fn` conventions. Documented in rustdoc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Route generated SessionId/RequestId fields through hand-authored newtypes

Generated code emitted `pub session_id: String` for every schema field named
`sessionId` and likewise for `requestId`, leaving consumers with mixed types:
`Session::id()` returned `SessionId` but `session.events_subscribe()` events
exposed `session_id: String`. Same papercut for request IDs in permission and
elicitation event payloads.

The newtypes are `#[serde(transparent)]` so the wire format is unchanged. This
adds a property-name override map to `scripts/codegen/rust.ts` that maps
`sessionId`, `remoteSessionId`, and `requestId` to the hand-authored types in
`crate::types`, and emits the matching `use` statement in both generated
modules. `mc_session_id` (MCP protocol metadata, not a Copilot session) stays
as `String`.

After regeneration: 27 fields converted to `SessionId` (including the handoff
event's `remoteSessionId`) and 25 to `RequestId`. The existing `PartialEq<str>`
/ `PartialEq<String>` impls on both newtypes mean test code like

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass ToolInvocation to define_tool closures

define_tool's Fn(P) -> Fut bound gave closures only the deserialized
arguments, leaving session_id, tool_call_id, and tool_name unreachable.
That blocked the helper for any tool that needs to scope DB lookups to
a session, emit per-tool-call telemetry, or stream UI updates back to
the originating session — patterns that hit dozens of sites across
realistic tool suites.

Change the closure bound to Fn(ToolInvocation, P) -> Fut. The arguments
are moved out via mem::take before deserialization, so there is no
clone cost on the hot path. Closures that don't need the metadata
write |_inv, params|.

Also add ToolInvocation::params<P>() so long-form impl ToolHandler
blocks can deserialize without naming serde_json directly:

    async fn call(&self, inv: ToolInvocation) -> Result<ToolResult, Error> {
        let params: MyParams = inv.params()?;
        // …use inv.session_id alongside params…
    }

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Make ping message argument optional

Node, Python, and .NET all expose ping with an optional message.
Go requires it only because Go has no Option type — Rust has one,
so the API should match the languages with the same expressive power
rather than the one without.

Change ping(&self, message: &str) to ping(&self, message: Option<&str>).
When None, the message field is omitted from the request payload
rather than sent as an empty string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Build Rust docs with all features in CI

cargo doc was running with --features test-support, which left the
derive feature off and made intra-doc links to define_tool and
schema_for resolve to nothing — failing under the crate's
deny(rustdoc::broken_intra_doc_links).

docs.rs already uses all-features (see Cargo.toml's
[package.metadata.docs.rs]); align CI with that so the docs job
matches what users will see on docs.rs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR #1164 review feedback

  emitted from the loop correlate to a session in traces. Matches
  the pattern documented in the rust-coding-skill.

- README.md / embeddedcli.rs: correct the embedded-CLI documentation
  to match what build.rs and embeddedcli.rs actually do — archives
  come from the github/copilot-cli GitHub Releases, integrity is
  SHA-256 against SHA256SUMS.txt, and the runtime cache path is
  ~/.cache/copilot-sdk-{version}/copilot.

- test/scenarios/sessions/streaming/verify.sh: drop a duplicate
  '# Go: build' comment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Regenerate Rust types for @github/copilot 1.0.39-0

Picks up the new model.call_failure session event (with its
ModelCallFailureData payload and ModelCallFailureSource enum) and
the new optional 'tip' field on session_info.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Scope codegen-check workflow changes to Rust only

Removes path triggers and the regenerate step for other languages'
protocol-version files. Those drift checks are a pre-existing gap on
main and out of scope for the Rust SDK port.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Point rust-publish-release workflow header to RELEASING.md

The 23-line setup checklist duplicated content already in
rust/RELEASING.md. One-line pointer is enough.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update Rust scenario binaries for new define_tool signature

Two scenarios still used the old `Fn(P) -> Fut` shape and broke when
the SDK switched to `Fn(ToolInvocation, P) -> Fut`. They don't use
the invocation field, so just bind it as `_inv`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename Session::send_message -> send and align MessageOptions

Cross-SDK consistency: every other SDK (Node, Python, Go, .NET) uses
`send`/`Send`/`SendAsync` plus `MessageOptions` as the public
parameter type. Rust was the outlier with `send_message` and
`SendOptions`, and the asymmetry with the existing `send_and_wait`
method made it read awkwardly.

- Rename `Session::send_message` -> `Session::send` (and the private
  helper `send_message_inner` -> `send_inner`).
- Rename the public `SendOptions` type -> `MessageOptions`.
- Delete the previous wire-level `MessageOptions` struct: it had no
  internal callers (the wire payload is hand-rolled in send_inner) and
  freeing the name was the cleanest path to parity.

Pre-1.0 type rename, no protocol or behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Wrap subscribe() in EventSubscription / LifecycleSubscription newtypes

Previously Session::subscribe and Client::subscribe_lifecycle returned
raw tokio::sync::broadcast::Receiver<T> values. A survey of mature Rust
crates (tonic, lapin, rdkafka, redis-rs, tokio-tungstenite, iroh-gossip,
tokio-stream's BroadcastStream itself) found that none of them expose a
raw broadcast::Receiver in their public API; the dominant pattern is a
named newtype implementing futures::Stream, with overflow surfaced
explicitly in the item type.

Introduce a copilot::subscription module with:

  - EventSubscription / LifecycleSubscription newtypes
  - Inherent recv() returning Result<T, RecvError> for existing
    while-let loop ergonomics
  - Stream impl yielding Result<T, Lagged> so callers can use
    tokio_stream::StreamExt or futures::StreamExt combinators
  - Lagged / RecvError types owned by the SDK so consumers no longer
    import tokio's broadcast error types

Net effect: the channel choice is now an internal implementation detail.
We can swap broadcast for async-broadcast / flume / a custom backpressure
policy, or convert lag into an Event::Lagged variant, without a breaking
change to the public surface.

Existing while-let loops in tests and examples continue to compile and
behave identically: close and lag both exit the loop, matching
tokio::sync::broadcast::Receiver.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Apply nightly rustfmt to subscription module

Local cargo +nightly fmt --check passed without `--config-path
.rustfmt.nightly.toml`, but CI runs with the explicit config and
flagged two diffs: import group flattening and test-mod import order.
Applied with the same flags CI uses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix workspaces RPC method names (was singular `workspace.*`)

`Session::list_workspace_files`, `read_workspace_file`, and
`create_workspace_file` were calling the singular `session.workspace.*`
RPC methods. The CLI exposes these under the plural namespace
`session.workspaces.*` (see Node `session.rpc.workspaces`, Go
`session.RPC.Workspaces`, .NET `session.Rpc.Workspaces`, Python
`session.rpc.workspaces`, and the generated `SESSION_WORKSPACES_*`
constants).

Route the three calls through the typed constants in
`generated::api_types::rpc_methods` so the bug class is structurally
gone — a stale string literal can no longer drift from the schema.

Add three integration tests against the mock server in
`session_test.rs` asserting the wire method name and request shape for
each helper. This mirrors the
`session_rpc_methods_send_correct_method_names` table-driven test that
already covers the rest of the session RPCs.

Also fold the missing `create_workspace_file` into the workspace
helpers bullet in CHANGELOG.md.

The bug never shipped — the SDK is pre-publish — so no migration
notes. Heads-up filed to the github-app sister copy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* rust: add typed RPC namespace, route helpers through it

Phase 4 § 4.6.A.5 — generate a fully-typed `client.rpc.*` /
`session.rpc.*` namespace from the Copilot CLI schema, mirroring the
Node/Python/Go/.NET SDKs. All hand-authored ergonomic helpers
(`list_workspace_files`, `read_plan`, `set_mode`, `list_models`,
`get_quota`, etc.) become one-line delegations over the namespace —
their public signatures are unchanged, but wire-method strings now
exist in exactly one place (`generated/rpc.rs`).

This makes the `session.workspace.*` → `session.workspaces.*` typo
bug class structurally impossible: helpers can't drift from the
schema because they no longer reference wire strings, and new RPCs
land in the namespace immediately as the schema regenerates.

- scripts/codegen/rust.ts: emit rust/src/generated/rpc.rs alongside
  api_types.rs. Builds a namespace tree from `rpcMethod` paths,
  resolves $ref/title/inline schemas for params + results, injects
  sessionId for session methods, dispatches via rpc_methods constants.
- rust/src/generated/rpc.rs: new ~1370 LOC generated file.
- rust/src/lib.rs: Client::rpc() accessor; ping/list_models/get_quota
  rewritten as delegations.
- rust/src/session.rs: Session::rpc() accessor; workspaces/plan/mode/
  model/name/log/fleet/permissions helpers rewritten as delegations.
- rust/tests/session_test.rs: add 3 namespace-only tests
  (session.rpc().agent().list, session.rpc().tasks().list,
  client.rpc().models().list); update session.log mock response to
  match typed LogResult shape (eventId required).
- rust/CHANGELOG.md, rust/README.md: document the new namespace.

cargo +nightly-2026-04-14 fmt --check, cargo clippy
--all-features --all-targets -- -D warnings, and cargo test
--all-features all green.

No breaking changes for SDK or github-app consumers — public helper
signatures are preserved by construction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename crate to `github-copilot-sdk`

Per formal naming decision, the published crate name on crates.io will
be `github-copilot-sdk` and the import path will be
`use github_copilot_sdk::...`.

Changes:
- `Cargo.toml`: `name = "github-copilot-sdk"`, `[lib] name = "github_copilot_sdk"`,
  `documentation = "https://docs.rs/github-copilot-sdk"`. Repository and
  homepage URLs continue to point at `github/copilot-sdk` (the repo
  name is unchanged).
- All `use copilot::` / `copilot::` references migrated to
  `use github_copilot_sdk::` / `github_copilot_sdk::` across `src/`,
  `tests/`, `examples/`, README, CHANGELOG, RELEASING.md, and codegen
  scripts.
- Embedded-CLI cache directory renamed from `~/.cache/copilot-sdk-*` to
  `~/.cache/github-copilot-sdk-*` for naming consistency. Pre-release
  caches will be orphaned; acceptable given the crate is unpublished.
- `release-plz.toml` package name updated.

Verified: `cargo check`, `cargo clippy -D warnings`, `cargo test`,
nightly `cargo fmt --check` all green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add typed wrappers for filter/MCP/permission shapes (Bucket A.1, A.3, A.4)

Replaces the remaining `serde_json::Value` blobs on the public surface
with typed structs/enums, mirroring the wire shapes documented in
Node/Go/Python/.NET. Helper signatures already changed from `Value` to
typed equivalents in the same call-sites; this lands the type
definitions and constructor-site updates.

A.3 — typed wrappers
* `SessionListFilter { cwd, git_root, repository, branch }` for
  `Client::list_sessions`. Was `Option<serde_json::Value>`.
* `McpServerConfig` tagged enum (Stdio/Http/Sse) with
  `McpStdioServerConfig` / `McpHttpServerConfig` payloads.
  `SessionConfig::mcp_servers`, `ResumeSessionConfig::mcp_servers`, and
  `CustomAgentConfig::mcp_servers` are now
  `Option<HashMap<String, McpServerConfig>>`. Accepts `type: "local"`
  alias on deserialize for back-compat with the legacy CLI shape.
* `PermissionRequestData` gains `kind: Option<PermissionRequestKind>`
  and `tool_call_id: Option<String>` fields. The eight CLI permission
  categories (shell/write/read/url/mcp/custom-tool/memory/hook) are
  enumerated; unknown kinds fall through to `Unknown`. Existing
  `extra: Value` flatten is preserved so `data.extra["command"]` etc.
  keep working.

A.4 — PermissionResult variants
* Adds `UserNotAvailable` (encodes as `{kind: "user-not-available"}`)
  and `NoResult` (encodes as `{kind: "no-result"}`). Both paths —
  notification (`handlePendingPermissionRequest`) and direct RPC
  (`permission.request`) — emit the correct kind string.
* `pending_permission_result_kind` updated to handle `NoResult`
  explicitly; `direct_permission_payload` updated to bypass
  ApproveOnce/Reject conversion for these new variants.

A.1 — disable_resume
* `ResumeSessionConfig::disable_resume: Option<bool>`. Mirrors Node's
  `ResumeSessionConfig.disableResume` and Go's `*bool DisableResume`.
  Forces resume to fail if the session is missing on disk rather than
  silently starting a new one.

Tests
* `list_sessions_serializes_typed_filter` — confirms the typed filter
  serializes camelCase, omits None fields, and dispatches via
  `session.list`.
* `mcp_server_config_roundtrips_through_tagged_enum` — confirms
  `type: "stdio"` serialize, `type: "local"` alias on deserialize, and
  HashMap roundtrip.
* `permission_request_data_extracts_typed_kind` — confirms typed kind
  extraction including the kebab-case `custom-tool` rename and the
  forward-compatible `Unknown` fallthrough.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document infinite_sessions parity + Client::stop deferral (Bucket A.2/A.6)

Notes-only CHANGELOG entries closing out the remaining Bucket A items:

* A.2 (infinite_sessions) — already wired on both SessionConfig and
  ResumeSessionConfig as `Option<InfiniteSessionConfig>` with
  default-omit-on-the-wire semantics. Matches Node/Go (CLI-applied
  defaults when the field is absent). No code change; document the
  parity decision so it doesn't get re-flagged.
* A.6 (Client::stop error aggregation) — defer behind a Client-level
  session registry. Real aggregation requires iterating Session handles
  to disconnect-then-kill, but the Rust Client only tracks per-session
  channel senders today (router::SessionSenders), not Session instances.
  A cosmetic Result<(), Vec<Error>> change with no behavior shift would
  be pure pass-through wrapping. Tracked for Bucket B alongside the
  registry refactor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Aggregate Client::stop errors across active sessions (Bucket B / A.6)

Rewrite `Client::stop` to cooperatively shut down every still-registered
session before terminating the CLI child:

  1. Snapshot active session IDs from the router (no lock held across
     the destroy RPCs).
  2. For each, send `session.destroy` and unregister.
  3. Take + terminate the child.
  4. Collect any errors from steps 2 and 3 into a new `StopErrors`
     aggregate and return `Result<(), StopErrors>`.

`StopErrors` is a public newtype around `Vec<Error>` implementing
`std::error::Error`; `errors()` and `into_errors()` expose the
underlying errors. Mirrors Node's `Error[]` shutdown return shape and
closes Bucket A.6 (deferred from the prior batch since true aggregation
required iterating active sessions, not just changing the signature).

Implementation detail: rather than introducing a parallel
`Weak<Session>` registry on the Client, this leans on the router's
existing session-ID HashMap. The router already tracks every session
the client has registered, so a `session_ids()` snapshot helper is the
only new state. No new Arc<Session> ceremony, no Drop-impl back-pointer,
no public API change to Session.

This is a breaking change to `Client::stop`'s return type. README's
quickstart example switches from `client.stop().await?` to
`client.stop().await.ok();` (best-effort shutdown) — callers that
care about per-session destroy errors can match on `StopErrors` and
inspect `errors()`.

Tests:
  - `client_stop_sends_session_destroy_for_each_active_session` — two
    registered sessions, both destroys observed on the wire, returns Ok.
  - `client_stop_aggregates_session_destroy_errors` — destroy returns
    a JSON-RPC error; `StopErrors` carries it and Display includes the
    underlying message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Bucket B.1 SessionConfig fields

Mirrors the six knobs the Node SDK exposes on `SessionConfig` /
`ResumeSessionConfig` that the Rust SDK was missing:

- session_id: Option<SessionId> on SessionConfig (custom session ID;
  remains required on ResumeSessionConfig as the existing field).
- working_directory: Option<PathBuf> (per-session cwd override,
  independent of ClientOptions::cwd).
- config_dir: Option<PathBuf> (override default config dir for this
  session).
- model_capabilities: Option<ModelCapabilitiesOverride> (per-property
  capability overrides, deep-merged at session create / resume time;
  the type was already used by SetModelOptions).
- github_token: Option<String> (per-session GitHub token, distinct
  from ClientOptions::github_token which authenticates the CLI process
  itself). Redacted from Debug output.
- include_sub_agent_streaming_events: Option<bool>.

Wire format: snake_case round-trips to camelCase via
#[serde(rename_all = "camelCase")] except gitHubToken, which uses an
explicit #[serde(rename = "gitHubToken")] to match Node's mixed-case
spelling.

Adds two regression tests in tests/session_test.rs verifying serde
output for the wire field names + Debug-redaction of the token.

CHANGELOG: documents the six new fields under "Configuration parity".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Bucket B.2 ClientOptions fields (log_level + idle timeout)

Mirrors two of the five Node CopilotClientOptions knobs the Rust SDK
was missing:

- log_level: Option<LogLevel> with a new typed enum (None / Error /
  Warning / Info / Debug / All), serialized lowercase to match the
  CLI's --log-level argument values. Replaces the previously hardcoded
  --log-level info in spawn_stdio / spawn_tcp; when unset the SDK still
  passes "info" for parity with prior behavior.
- session_idle_timeout_seconds: Option<u64>. When Some(n) with n > 0,
  the SDK passes --session-idle-timeout <n>. None or Some(0) leaves
  sessions running indefinitely (CLI default). Implementation lives
  in a new Client::session_idle_timeout_args helper, mirroring
  auth_args.

Adds four lib unit tests covering the helper plus LogLevel serde
round-trip.

Two of the five Node knobs are intentionally not ported and are
documented in the CHANGELOG as N/A:

- isChildProcess: requires a parent-stdio transport variant the Rust
  SDK does not yet support; tracked as a future addition rather than
  a Bucket B item.
- autoStart: does not apply to the Rust SDK's API shape — Client::start
  is a single explicit constructor rather than the deferred-init
  pattern Node uses.

The remaining onListModels (BYOK callback) is tracked separately;
its callback type design is non-trivial and warrants its own commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Bucket B.2 on_list_models BYOK callback override

Mirrors Node's `CopilotClientOptions.onListModels`: when set,
`Client::list_models` returns the handler's result without making
a `models.list` RPC. This is the BYOK escape hatch for environments
where the model catalog is provisioned separately from the CLI
(e.g. external inference servers selected via Transport::External).

API additions:
- `ListModelsHandler` async_trait with a single
  `async fn list_models(&self) -> Result<Vec<Model>, Error>` method.
  Mirrors the shape of `SessionHandler` / `SessionHooks` for
  consistency.
- `ClientOptions::on_list_models: Option<Arc<dyn ListModelsHandler>>`.
- `ClientOptions` switches from `#[derive(Debug)]` to a manual Debug
  impl that prints the handler as `<set>` / `None`. Same precedent
  as `SessionConfig::handler` and the redacted `github_token` field.

Plumbing:
- `ClientInner` gains an `on_list_models` field carrying the handler.
- `Client::from_transport` takes the handler as a new parameter;
  threaded through all 3 transport call sites in `Client::start`
  (External / Tcp / Stdio). `Client::from_streams` (no
  ClientOptions) passes None.
- `Client::list_models` consults the handler before falling back
  to the RPC.

Tests:
- `client_options_debug_redacts_handler`: confirms manual Debug
  prints `<set>` for the handler and continues redacting
  `github_token`.
- `list_models_uses_on_list_models_handler_when_set`: end-to-end
  override path with a counting handler.

Bucket B.2 is now complete: log_level, session_idle_timeout_seconds,
and on_list_models all ported. is_child_process and auto_start
remain documented as N/A (transport variant Rust doesn't have / API
shape mismatch).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add MessageOptions.request_headers (Phase 4 § 4.5)

Mirrors Node's `MessageOptions.requestHeaders` and Go's
`MessageOptions.RequestHeaders`: custom per-turn HTTP headers
forwarded to the CLI on the `session.send` request, used by the
CLI to inject headers into outbound model API calls for that turn.

API additions:
- `MessageOptions::request_headers: Option<HashMap<String, String>>`
  (already `#[non_exhaustive]` with builder pattern, so the new field
  is fully additive for existing callers).
- `MessageOptions::with_request_headers(headers)` chainable builder.

Wire format:
- `session.send` request payload gains a `requestHeaders` object
  when the field is `Some(map)` and the map is non-empty.
- Omitted entirely when `None` or empty — same `omitempty` semantics
  as Node's optional field (per Node types.ts:1521-1524).

Tests:
- `send_serializes_request_headers` — multi-header case verifies
  field name (camelCase `requestHeaders`) and value pass-through.
- `send_omits_request_headers_when_unset_or_empty` — covers both
  the unset (None) and empty-map cases, ensuring neither sends
  `"requestHeaders": {}` on the wire.

Bucket scope: this is the smallest of Phase 4's four 1.0-blocking
gaps. 4.1 (slash commands) and 4.2 (SessionFsProvider, ADR-worthy)
remain. 4.3 / 4.4 are pending team review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add slash command registration (Phase 4 § 4.1)

Introduces the consumer-facing surface for `/cmdname` slash commands,
matching Node's `SessionConfig.commands`, Python's, and Go's:

- `CommandHandler` async trait — single `on_command(ctx)` async method.
  Mirrors the SDK's existing `SessionHandler` / `ToolHandler` pattern
  (named trait, navigable in stack traces) over a boxed-closure shape.
- `CommandDefinition { name, description?, handler }` — `#[non_exhaustive]`
  with `new` + `with_description` builders. Manual `Serialize` emits only
  `{name, description?}` on the wire; handlers stay client-side. Manual
  `Debug` prints the handler as `<set>` (same precedent as
  `SessionConfig::handler`).
- `CommandContext { session_id, command, command_name, args }` —
  `#[non_exhaustive]`, what the handler receives.
- `SessionConfig::commands` and `ResumeSessionConfig::commands` —
  `Option<Vec<CommandDefinition>>` with `with_commands` builders. Field
  is `skip_deserializing` since `CommandDefinition` carries an opaque
  handler.

Wire-up:

- `Client::create_session` / `Client::resume_session` drain
  `config.commands` into an `Arc<HashMap<String, Arc<dyn CommandHandler>>>`
  via a new `build_command_handler_map` helper, threaded through
  `spawn_event_loop` to `handle_notification`.
- New `SessionEventType::CommandExecute` arm dispatches incoming
  `command.execute` notifications: looks up the handler by `commandName`,
  invokes it on a spawned task, then acks via
  `session.commands.handlePendingCommand` — no error on success,
  `error: <handler message>` on `Err`, and
  `error: "Unknown command: <name>"` when the name is unregistered
  (matches Node's behavior verbatim).

Tests (4 new in `rust/tests/session_test.rs`, all green):

- `create_serializes_commands_strips_handler` — wire payload contains
  only `{name, description?}`, no `handler` key.
- `command_execute_dispatches_to_registered_handler_and_acks_success`
  — handler invoked with right `CommandContext`, ack has no error.
- `command_execute_unknown_command_acks_with_error` — unregistered
  command name produces `Unknown command: <name>` ack.
- `command_execute_handler_error_propagates_to_ack` — handler `Err` is
  surfaced verbatim in the ack's `error` field.

Schema-side types (`CommandExecuteData`,
`SessionRpcCommands::handle_pending_command`, `RPC_METHOD_*`) were
already in `rust/src/generated/` — no codegen changes needed.

Cross-repo impact (additive): github-app's `SessionConfig { ... }`
literal at `core.rs:710` will need `commands: None,` added at sync time,
same mechanical pattern as Bucket B.1 fields.

CHANGELOG entry added under "Configuration parity".

Gates:
- cargo +nightly-2026-04-14 fmt --check ✅
- cargo clippy --all-features --all-targets -- -D warnings ✅
- cargo test --all-features ✅ (84 lib + 5 + 3 + 68 + 17 = 177 tests)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add ADR 0001: SessionFsProvider trait and plumbing (Phase 4 § 4.2)

Drafts the architecture decision record for the last 1.0 parity gap —
SessionFsProvider — before any implementation lands. Per the global
"ADRs for major decisions" convention and tclem's explicit guidance
on §4.2.

Establishes `rust/docs/adr/` as the home for Rust SDK ADRs (this is
the first one) with a short index in `rust/docs/adr/README.md`.

ADR scope:

- **Methodology** — explicit verify-before-drafting audit, citing the
  consistent wins from A.2 / A.6 / 4.5 / 4.1. Captures the cross-SDK
  source map (Node + Python + Go + generated Rust types) so reviewers
  can verify the proposed shape against actual upstream code.
- **Trait shape** — async_trait with 10 methods mirroring Node's
  provider, returning `Result<T, FsError>`. Sync alternative and
  trait-erased boxed-closure alternative both rejected with rationale.
- **Method signatures** — Rust-idiomatic `Result<T, FsError>` with the
  SDK adapting to the schema's `{ ..., error: Option<SessionFsError> }`
  payload. `FsError::NotFound` → `ENOENT`, `FsError::Other` → `UNKNOWN`.
  `From<io::Error>` provided so `tokio::fs`-backed handlers can `?`.
- **Concurrency model** — concurrent dispatch (each `sessionFs.*`
  request on its own spawned task), `Send + Sync` providers must be
  re-entrant. Per-session sequential dispatch rejected with rationale
  (CLI parallelism assumption).
- **Plumbing** — direct `Arc<dyn SessionFsProvider>` registration on
  SessionConfig, NOT a factory closure like Node/Python/Go. Sidesteps
  the "lambdas as fn args" rule, idiomatic Rust, callers can carry
  session-id refs themselves. Trade-off documented in "Differences
  from other SDKs" call-out planned for the README. Future
  factory-closure form can land additively if needed.
- **Inbound dispatch** — new arms in `handle_request` for all 10
  `sessionFs.*` methods, dispatched through a per-session
  `Arc<HashMap<SessionId, Arc<dyn SessionFsProvider>>>` map mirroring
  the §4.1 `command_handlers` shape.
- **Naming + module layout** — `rust/src/session_fs.rs` for public
  surface, `rust/src/session_fs_dispatch.rs` (`pub(crate)`) for the
  request adapters. `SessionFsConventions { Posix, Windows }` is
  hand-authored to avoid the generated enum's catch-all `Unknown`
  variant on the consumer-input side.
- **Forward compat** — `#[non_exhaustive]` on `SessionFsConfig`,
  `FsError`, `FileInfo`, `DirEntry`, `DirEntryKind`. Future trait
  methods land with default impls returning `Err(FsError::Other(...))`
  so existing impls continue to compile.
- **Tauri-app non-impact** — audited; `src-tauri/` does not register
  a virtual filesystem provider today. `ClientOptions { ... }` literal
  in `cli.rs` will need `session_fs: None,` added at sync time, same
  mechanical pattern as Bucket B.2.
- **Implementation order** — 11-step ordered plan, from trait
  authoring through CHANGELOG entry, so the actual implementation
  commit can follow ADR approval without sequencing surprises.

This commit ships the ADR only — no implementation code, no public
API changes, no test changes. The ADR is `Status: Proposed` until
tclem and the Rust SDK working group sign off.

ASCII-only diagrams per repo style. No external markdown rendering
dependencies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* rust: implement SessionFsProvider (Phase 4 § 4.2)

Adds a virtualizable filesystem provider that lets host applications
sandbox sessions, project files into in-memory or remote storage, and
apply permission policies before bytes move. Last 1.0-blocking parity
gap before public release.

Public API additions (in `crate::session_fs`, re-exported from
`crate::types`):

- `SessionFsProvider` async trait with 10 methods (`read_file`,
  `write_file`, `append_file`, `exists`, `stat`, `mkdir`, `readdir`,
  `readdir_with_types`, `rm`, `rename`). All methods have default impls
  returning `Err(FsError::Other("not supported"))` so providers only
  override what they need and forward-compatible schema additions land
  without breaking implementors.
- `SessionFsConfig` (initial_cwd, session_state_path, conventions),
  `SessionFsConventions` (Posix/Windows), `FsError` (NotFound/Other),
  `FileInfo`, `DirEntry`, `DirEntryKind`. All `#[non_exhaustive]`.
- `ClientOptions::session_fs: Option<SessionFsConfig>` — when set,
  `Client::start` calls `sessionFs.setProvider` after protocol-version
  verification.
- `SessionConfig::with_session_fs_provider` /
  `ResumeSessionConfig::with_session_fs_provider` builders for
  registering an `Arc<dyn SessionFsProvider>` per session.
- `From<std::io::Error>` on `FsError` (NotFound→NotFound, anything
  else→Other) so handlers backed by `std::fs` / `tokio::fs` can use `?`.

Wire dispatch:

- `pub(crate) crate::session_fs_dispatch` module bridges the trait to
  the schema. `FsError::NotFound` maps to wire `ENOENT`; all other
  errors map to `UNKNOWN` with the message preserved for diagnostics.
- The session event loop forwards inbound `sessionFs.*` requests to
  the dispatch module; non-fs methods continue to the existing
  `unknown method` error path.
- New `SessionError::SessionFsProviderRequired` and
  `SessionError::InvalidSessionFsConfig` variants surface
  configuration errors at `Client::start` / `create_session` time.

Divergence from Node/Python/Go:

- This SDK accepts `Arc<dyn SessionFsProvider>` directly, rather than a
  factory closure that builds a provider per session. There is no
  `Session` value to thread into a factory at config time, and the SDK
  already prefers traits over boxed closures for handler-shaped APIs
  (`SessionHandler`, `SessionHooks`, `ToolHandler`). See
  `rust/docs/adr/0001-session-fs-provider.md` for the rejected-factory
  rationale and a forward-compat escape hatch
  (`with_session_fs_provider_factory`) that can be added additively
  post-1.0 if a real factory use case emerges. ADR status flipped from
  Proposed to Accepted in this commit.

Tests:

- 7 mock-server tests in `rust/tests/session_test.rs` covering
  read_file dispatch, NotFound→ENOENT mapping, Other→UNKNOWN mapping,
  write_file with mode, readdir_with_types, rm with force, and
  validation rejecting empty `initial_cwd`.
- Inline unit tests in `session_fs.rs` for the io::Error→FsError
  conversion.

Docs / examples:

- `rust/examples/session_fs.rs` — in-memory provider example.
- `rust/CHANGELOG.md` — entry under "Configuration parity".
- `rust/README.md` — new "Differences From Other SDKs" section
  linking to the ADR.

Verified:

- `cargo +nightly-2026-04-14 fmt --check`
- `cargo clippy --all-features --all-targets -- -D warnings`
- `cargo test --all-features` (190 tests pass; 3 ignored require CLI)

Cross-repo impact (additive only):

- `src-tauri/src/session/cli.rs:337` `ClientOptions { ... }` literal —
  adds `session_fs: None`.
- `src-tauri/src/session/core.rs:710` `SessionConfig { ... }` literal —
  adds `session_fs_provider: None`.
- `src-tauri/src/session/core.rs:749` `ResumeSessionConfig { ... }`
  literal — adds `session_fs_provider: None`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add W3C Trace Context propagation (Phase 4 § 4.3)

Implements the cross-SDK 1.0-blocking `traceparent` / `tracestate` plumbing
across the three RPC injection points and the inbound tool-invocation read
path. Hybrid shape combines Node's callback ergonomics with Go's per-turn
override:

- New `TraceContext` struct (`#[non_exhaustive]`) and `TraceContextProvider`
  async trait in `crate::trace_context`, re-exported from `crate::types`.
- `ClientOptions::on_get_trace_context: Option<Arc<dyn TraceContextProvider>>`
  supplies an ambient provider invoked on `session.create`, `session.resume`,
  and `session.send`. Manual `Debug` impl prints `<set>` / `None` matching
  the `on_list_models` precedent.
- `MessageOptions` gains `traceparent` / `tracestate: Option<String>` plus
  three builders — `with_trace_context`, `with_traceparent`,
  `with_tracestate`. Per-turn values override the callback (provider is not
  invoked when MessageOptions already carries trace headers).
- `ToolInvocation` flipped to `#[non_exhaustive]` and exposes inbound
  `traceparent` / `tracestate` populated from `external_tool.requested`
  events, plus a `trace_context()` helper. Wire fields are omitted when
  unset, matching the schema's `omitempty` semantics.
- New test-only constructor `Client::from_streams_with_trace_provider`
  (gated on `cfg(test)` / `feature = "test-support"`) so integration tests
  can exercise the callback path.

Tests: 7 new mock-server scenarios in `tests/session_test.rs` covering
provider invocation on create/resume/send, MessageOptions-overrides-callback,
MessageOptions-without-callback, ToolInvocation read path, and wire-omits-
when-unset. Full suite: 88 lib + 83 integration green; clippy and nightly
fmt clean.

No formal ADR — `ListModelsHandler` and `MessageOptions` precedents already
cover both shapes. CHANGELOG entry under "Configuration parity" documents
the cross-SDK rationale.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Implement Default on ToolInvocation for test ergonomics

Adds `#[derive(Default)]` to `ToolInvocation` (and to `SessionId`, which
the derive depends on). Pure additive: production code never constructs
`ToolInvocation` literals (it's a CLI-emitted read-only type), so the
"meaningless empty defaults" cost is genuinely zero in practice.

The win is downstream test scaffolding. With `..Default::default()`
available, test sites that build `ToolInvocation` literals to drive
handler tests can collapse boilerplate and absorb future
`#[non_exhaustive]` field additions automatically — no need to
re-touch every test scaffold every time the schema grows. Aligns with
the rust-coding-skill's "use `..Default::default()` in tests to reduce
boilerplate when adding fields" guidance, and mirrors `MessageOptions`
which already derives `Default`.

`SessionId` is a transparent newtype around `String`, so its `Default`
is `SessionId(String::new())` — already the natural identity for an
unset session ID.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add TelemetryConfig env-var passthrough on ClientOptions (Phase 4 § 4.4)

Closes the last 1.0-blocking gap in Phase 4 of the Rust SDK release prep.
Adds opt-in OpenTelemetry configuration mirroring Node/Python/Go.

## Surface

- `TelemetryConfig` (`#[non_exhaustive]`, `Debug + Clone + Default`) with
  five `Option`-typed fields: `otlp_endpoint`, `file_path`,
  `exporter_type`, `source_name`, `capture_content`. No `Serialize` /
  `Deserialize` derives — this type is not wire-serialized; it's pure
  spawn-time env-var injection.
- `OtelExporterType` enum (`#[non_exhaustive]`, `Serialize`/`Deserialize`
  with `rename_all = "kebab-case"`): `OtlpHttp`, `File`. Typed rather than
  `Option<String>` to match the `LogLevel` precedent (B.2) for finite,
  enumerated CLI knobs.
- `ClientOptions::telemetry: Option<TelemetryConfig>`, defaulting to
  `None`. Field added to the manual `Debug` impl.

## Wire behavior

When `ClientOptions::telemetry` is `Some(...)`, `Client::build_command`
sets `COPILOT_OTEL_ENABLED=true` plus, for each populated field:

- `otlp_endpoint`         -> `OTEL_EXPORTER_OTLP_ENDPOINT`
- `file_path`             -> `COPILOT_OTEL_FILE_EXPORTER_PATH`
- `exporter_type`         -> `COPILOT_OTEL_EXPORTER_TYPE` (`"otlp-http"` / `"file"`)
- `source_name`           -> `COPILOT_OTEL_SOURCE_NAME`
- `capture_content`       -> `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`
                             (`"true"` / `"false"`)

Env-var names are byte-for-byte identical to the Node/Python/Go SDKs.
`capture_content` serializes as lowercase `"true"`/`"false"` matching
Node's `String(t.captureContent)` and Python's `str(...).lower()`.

## Env-var precedence

`build_command` env order: auth token -> telemetry -> `options.env`
(user override) -> `options.env_remove`. User-supplied env always wins
over both auth and telemetry, by ordering. Tests cover this.

## Design notes

- Pure env-var passthrough: zero new dependencies. The CLI itself owns
  the OpenTelemetry exporter; the SDK is just a config conduit. All
  three reference SDKs (Node, Python, Go) work this way — verified
  before drafting (verify-before-drafting tally now at 8 wins).
- No ADR. Plan-doc explicitly endorsed skipping the ADR since this
  isn't an architectural decision: the shape is fully determined by
  cross-SDK precedent. ADR directory is reserved for non-obvious
  choices; precedent verification belongs in CHANGELOG + commit body.
- `#[non_exhaustive]` on both `TelemetryConfig` and `OtelExporterType`
  to absorb future CLI-side telemetry knobs (sampling, additional
  exporters) without breaking changes.
- `PathBuf` (not `String`) for `file_path` for type safety;
  `Command::env` accepts `AsRef<OsStr>` so no conversion needed.

## Tests

Five new tests in `lib.rs`:

- `build_command_sets_otel_env_when_telemetry_enabled` — full
  TelemetryConfig set; asserts all six expected env vars propagate with
  correct values.
- `build_command_omits_otel_env_when_telemetry_none` — default
  `ClientOptions`; asserts none of the six env vars are present.
- `build_command_omits_unset_telemetry_fields` — only `otlp_endpoint`
  set; asserts that single field plus the implicit enabled flag are
  set, and the four others are absent.
- `build_command_lets_user_env_override_telemetry` — telemetry sets
  `OTEL_EXPORTER_OTLP_ENDPOINT="http://from-config:..."`, user env
  sets it to `"http://from-user-env:..."`; asserts user env wins.
- `telemetry_config_capture_content_serializes_as_lowercase_bool` —
  asserts both `Some(true)` and `Some(false)` serialize as the bare
  lowercase boolean strings.

All tests pass; `cargo fmt --check`, `cargo clippy -- -D warnings`,
`cargo test --all-features` all green.

## Cross-repo impact

Existing `ClientOptions { ... }` literal in github-app `cli.rs:337`
gains one new field: `telemetry: None`. No other consumer-side
changes. The Tauri app's existing telemetry pipeline is for the host
app and doesn't interact with the SDK config — this is purely about
forwarding telemetry env vars to the spawned CLI child.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document Rust-only API surface (Phase 4 § 4.7)

Doc-only follow-up to the Phase 4 parity work. Two pieces of content,
no API changes.

## Changes

1. **`Client::get_quota` rustdoc** — flags the method as Rust-only as
   of 0.1.0. The underlying `account.getQuota` JSON-RPC endpoint is
   exposed only by the Rust SDK in this release; Node, Python, Go, and
   .NET don't surface it. Brief, factual, no backport-issue links.

2. **"Rust-only API" subsection in `rust/README.md`** — restructures
   the existing "Differences From Other SDKs" section into two
   subsections: "Shape divergence" (the existing `SessionFsProvider`
   factory-vs-direct discussion) and a new "Rust-only API" listing the
   surface that exists only on the Rust SDK as of 0.1.0:

   - `Client::get_quota`
   - First-class `Session` convenience methods (`set_mode`/`get_mode`,
     `set_name`/`get_name`, `read_plan`/`update_plan`/`delete_plan`,
     `start_fleet`, `list_workspace_files`/`read_workspace_file`/
     `create_workspace_file`)
   - Typed newtypes (`SessionId`, `RequestId`)
   - Permission policy builders (`permission::approve_all`,
     `permission::deny_all`, `permission::approve_if`)
   - `Client::from_streams` (arbitrary `AsyncRead`/`AsyncWrite`)
   - `enum Transport { Stdio, Tcp, External }`
   - Split `prefix_args` / `extra_args`

   Tone is "Rust gets to be Rust" — not apologetic, no backport
   promises. Cross-SDK parity for these is explicitly framed as a
   post-release conversation, not a release blocker.

## Bonus: drive-by intra-doc link fix

Fixed a pre-existing broken intra-doc link in `session_fs.rs` (`[`tokio::fs`]`
-> explicit URL link). Caught by `cargo doc --no-deps --all-features`,
which now builds clean. Doing it here rather than as a separate commit
since the testing-pass that comes next is going to require clean docs
output anyway, and the fix is one line.

`cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo test --doc`,
and `cargo doc --no-deps --all-features` all green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Broaden skills discovery wording in copilot-instructions.md

Skills under .github/skills/ are auto-discovered by Copilot tooling
broadly (CLI, Copilot Coding Agent, etc.), not just Copilot CLI.
Per stephentoub's review on PR #1164.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix ConnectionState::Errored wire form to match Go ("error" not "errored")

Caught by stephentoub on PR #1164. ConnectionState's serde
`rename_all = "lowercase"` was producing "errored" for the Errored
variant, but Go's source-of-truth uses "error". Add explicit
`#[serde(rename = "error")]` on the variant so the wire form matches
Go's ConnectionState string. Variant name stays `Errored` to avoid
shadowing the std `Error` trait and the crate's own `Error` type.

Adds two unit tests locking in the wire format for all four variants.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename ConnectionState::Errored to ConnectionState::Error

Per follow-up on stephentoub's review of PR #1164: prefer renaming the
variant to drop the `#[serde(rename = "error")]` attribute. The variant
is unused outside types.rs (the Client transitions Disconnected /
Connecting / Connected today; Error is reserved for future use), so
renaming has no consumer impact and produces a cleaner enum surface.

`ConnectionState::Error` does not collide with anything in scope:
`types.rs` does not import `crate::Error` (it uses fully-qualified
`crate::Error` at use sites) and `std::error::Error` is unimported. The
variant lives inside a typed enum, so no shadowing concern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR #1164 cross-SDK consistency review

Three review-feedback fixes folded into a single commit:

- `session.ui()` sub-API: move `elicitation`, `confirm`, `select`,
  `input` onto a new `SessionUi<'a>` view returned by `Session::ui()`.
  Mirrors .NET `session.UI`, Python `session.ui`, Go `session.UI()`.
  Wire-method names unchanged. Per stephentoub on
  https://github.com/github/copilot-sdk/pull/1164#discussion_r3161986035
  and the bot duplicate at #discussion_r3163241529.

- Typed `Client::get_status` and `Client::get_auth_status` returns:
  introduce `GetStatusResponse { version, protocol_version }` and
  `GetAuthStatusResponse { is_authenticated, auth_type, host, login,
  status_message }`, both `#[non_exhaustive]`. Matches Node / Go /
  Python typed shapes. Per the bot at
  https://github.com/github/copilot-sdk/pull/1164#discussion_r3162629019.

- `Session::set_model` now returns `Result<(), Error>` instead of
  `Result<Option<String>, Error>`. Mirrors Node / Python / Go / .NET
  void-on-success semantics. Per the bot at
  https://github.com/github/copilot-sdk/pull/1164#discussion_r3162816817.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Type MessageOptions::mode as DeliveryMode enum

Promote MessageOptions::mode from Option<String> to Option<DeliveryMode>,
where DeliveryMode is a #[non_exhaustive] enum with Enqueue (default) and
Immediate variants serializing to "enqueue" / "immediate". Mirrors Node
types.ts:1519 (mode?: "enqueue" | "immediate") and Go types.go:858
(MessageOptions.Mode is the message delivery mode (default: "enqueue")).

The prior rustdoc on MessageOptions::mode incorrectly described the field
as a permission mode. The field has always controlled message delivery
relative to in-flight session work; the doc lied. Strings other than
"enqueue" / "immediate" were silently no-ops at the CLI, so the typed enum
turns wrong-string-at-call-site from a runtime nothing-happens into a
compile error.

Precedent for typed enums on enumerated CLI knobs: B.2 LogLevel
(c4132c2), 4.4 OtelExporterType (aefb108), SessionFsConventions, and
DirEntryKind. The asymmetric request_headers (4.5) stayed
HashMap<String, String> because that value space is open; mode is finite
and CLI-controlled.

with_mode signature shrinks from impl Into<String> to DeliveryMode and
the wire injection in session.rs uses serde_json::to_value(m). Added a
roundtrip test in types.rs covering both variants.

CHANGELOG entry under Configuration parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Default permission-flow flags to Some(true)

SessionConfig::default() and ResumeSessionConfig::new() now set the four
permission-flow flags to Some(true):

  - request_user_input
  - request_permission
  - request_exit_plan_mode
  - request_elicitation

Mirrors Node's client.ts:749-751 which always sets requestPermission: true
and derives requestUserInput/requestElicitation from handler presence. In
Rust, SessionHandler is trait-based so a handler is always installed
(DenyAllHandler is the default) — opt-in defaults of None meant a Rust
caller could install a SessionHandler and forget to flip the flags,
silently breaking permission flow vs. the equivalent Node code.

The default DenyAllHandler refuses all permission requests so the wire
surface is safe out-of-the-box. Callers that want the wire surface fully
disabled set the flags explicitly to Some(false).

SessionConfig drops its derived Default in favor of a manual impl that
enumerates every field. ResumeSessionConfig::new() switches the four
field assignments from None to Some(true). Field rustdoc updated on each
of the four fields to call out the default. Two unit tests added in
types.rs covering both constructors.

CHANGELOG entry under Configuration parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Mark remaining public config types non_exhaustive

Adds #[non_exhaustive] to the 10 remaining public configuration types
that didn't already carry the attribute:

  - SessionConfig
  - ResumeSessionConfig
  - ClientOptions
  - ProviderConfig
  - McpServerConfig
  - Tool
  - CustomAgentConfig
  - InfiniteSessionConfig
  - SystemMessageConfig
  - ConnectionState

HookEvent, HookOutput, MessageOptions, TelemetryConfig, SessionFsConfig,
FsError, FileInfo, DirEntry, ToolInvocation, Error, Transport, and the
new DeliveryMode were already marked. Closing the asymmetry now means
adding fields to any of these post-1.0 is non-breaking on consumers that
construct via Default::default() plus field assignment or the with_*
builders.

Tradeoff: external crates can no longer use struct-literal syntax for
these types -- not even with ..Default::default(), which only works
inside the defining crate. Tests, examples, and the tool_parameters
doctest are migrated to the let-mut + field-assignment pattern. Callers
porting from 0.1.0-* will see the same compile error and apply the same
mechanical transform.

CHANGELOG entry under Configuration parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix InputOptions doc-link to SessionUi::input

The UI-grouping commit f4aa8d9 moved input/select/confirm/elicitation off
Session into SessionUi, but the rustdoc on InputOptions still referenced
the old crate::session::Session::input path. Cargo doc with
-D rustdoc::broken_intra_doc_links rejected the link.

Repoints the link to crate::session::SessionUi::input.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Drop cross-SDK comparisons from Rust source comments

Per-symbol "Mirrors Node's Foo / Go's Bar / Python's Baz" rustdoc is
unscalable and drifts as the other SDKs evolve. The Rust SDK seeks parity
with Node/Python/Go/.NET; that fact is now stated once at the top of
rust/README.md, and intentional divergences live in the README's
"Differences From Other SDKs" section.

- Strip "Mirrors X" / "Unlike Y" / cross-SDK file:line citations from
  rustdoc across lib.rs, session.rs, types.rs, session_fs.rs,
  trace_context.rs. Replace with API-shape descriptions where the dropped
  text carried real information (e.g. wire-string variants).
- README: add a one-line parity statement up top that points to the
  existing Differences section.
- Update .github/skills/rust-coding-skill/SKILL.md to forbid cross-SDK
  references in code comments and rustdoc going forward, with explicit
  guidance that intra-SDK self-references ("Mirrors `from_streams`") are
  still fine.

cargo doc -D warnings clean. cargo clippy clean. cargo fmt clean.
18 doctests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Move SessionFs ADR out of public crate

ADR 0001 (SessionFsProvider trait and plumbing) captured the design
rationale for choosing direct Arc<dyn SessionFsProvider> registration
over the factory-closure pattern that Node/Python/Go use. That
rationale is internal release-engineering history, not consumer-facing
API documentation; the README's "Differences From Other SDKs" section
already explains what consumers need to know about the divergence.

The full ADR is being relocated to the private SDK release-plan
documentation in github/github-app PR #3166 (docs/copilot/2026-04-14-sdk-release/).

- Delete rust/docs/ entirely (only contained ADR 0001 + index README).
- Strip ADR links from rustdoc, README, CHANGELOG, and the
  session_fs example. The README's existing inline rationale (factory
  pattern doesn't cleanly express in Rust at the session-config call
  site, no `Session` value to thread in, SDK already prefers traits
  over closures for handler-shaped APIs) is sufficient on its own.

cargo doc -D warnings clean. cargo clippy clean. cargo fmt clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix SessionUi::elicitation wire field name

The hand-authored `SessionUi::elicitation` convenience layer was
sending the JSON Schema payload as `"schema"` on the wire, but the
`session.ui.elicitation` request shape expects `"requestedSchema"`.
This is the field name in:

- this crate's own generated UIElicitationRequest type
  (rust/src/generated/api_types.rs:1721, serde renames to
  `requestedSchema` via #[serde(rename_all = "camelCase")])
- the generated typed RPC wrapper SessionRpcUi::elicitation
  (rust/src/generated/rpc.rs:1245-1257), which is correct
- and the same wire field used by every other SDK we ship

So every elicitation call from the SessionUi convenience layer was
effectively dead — the CLI saw a missing required `requestedSchema`
field. The `confirm` / `select` / `input` helpers all delegate to
`elicitation`, so they were dead too.

The mock-server test for elicitation round-tripped through the same
misnamed field on both ends, so the bug slipped past unit tests
(`assert_eq!(request["params"]["schema"], schema)` matched the buggy
implementation). The fix is a one-line rename in session.rs plus a
test update that now asserts on `requestedSchema` and explicitly
rejects a stray `schema` key, so we can't regress the same way.

207 tests pass. doc / clippy / fmt clean.

Caught by the gap-analysis re-run before 0.1.0 cut. Wire-shape
divergence between hand-authored and generated layers — same class
of bug as the workspace RPC fix earlier in this stack.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add typed on_auto_mode_switch handler for rate-limit recovery

Wires the CLI's `autoModeSwitch.request` JSON-RPC callback (added by
copilot-agent-runtime PR #7024 — github/copilot-agent-runtime#7024) into
the SDK's typed handler surface. When an eligible rate limit is hit,
the runtime asks the SDK whether to switch the session to auto model;
this commit gives consumers a typed entry point for that prompt
matching the shape of the existing exit-plan-mode / user-input /
elicitation handshakes.

Background: the github-app's vendored copilot-sdk crate has carried a
private version of this handler since Apr 27 (github-app commit
02ec73588). The plumbing was never upstreamed because no other SDK had
a typed handler for it, and the wire-protocol gap on the runtime side
hadn't yet closed. With #7024 merged, the wire path is well-defined
(`autoModeSwitch.request` → `{ response: "yes" | "yes_always" | "no" }`)
and the typed handler can ship.

Cross-SDK divergence: typed handler exists only in the Rust SDK as of
0.1.0. Node, Python, Go, and .NET observe the request as a raw
JSON-RPC callback today; parity ports for those SDKs are post-release
follow-up work and are noted in the README's "Rust-only API" section
and in the changelog.

Wire shape and types:

- `handler::AutoModeSwitchResponse` — typed enum (`Yes`, `YesAlways`,
  `No`) with `#[serde(rename_all = "snake_case")]`. Wire values are
  byte-identical to the runtime's schema. `#[non_exhaustive]` so future
  variants are additive. Ships as a typed enum rather than the
  `{ response: String }` shape used in the github-app vendored copy —
  consistent with the recently-landed `DeliveryMode` enum and the
  general convention that finite, enumerated wire values get typed at
  the API surface (LogLevel, OtelExporterType, ConnectionState,
  PermissionRequestKind, etc.).
- `handler::HandlerEvent::AutoModeSwitch { session_id, error_code,
  retry_after_seconds }` — new event variant. `error_code` is the
  rate-limit kind (e.g. `user_weekly_rate_limited`). `retry_after_seconds`
  is the RFC 9110 `Retry-After` `delta-seconds` value when the runtime
  knows it; consumers can surface a humanized reset time alongside the
  prompt.
- `handler::HandlerResponse::AutoModeSwitch(AutoModeSwitchResponse)` —
  new response variant.
- `SessionHandler::on_auto_mode_switch` — new trait method with safe
  default (`No`). Default `on_event` dispatches to it.
- `types::SessionConfig::request_auto_mode_switch: Option<bool>` and
  `types::ResumeSessionConfig::request_auto_mode_switch: Option<bool>`
  — new opt-in flags, both default to `Some(true)` via
  `SessionConfig::default()` and `ResumeSessionConfig::new()` to match
  the convention already established by the other four `request_*`
  flags. Without the flag the runtime doesn't dispatch the callback.
- `session.rs` `handle_request` — new arm for `autoModeSwitch.request`
  alongside the existing `permission.request` / `userInput.request` /
  `exitPlanMode.request` cases. Extracts `errorCode` /
  `retryAfterSeconds` from params, dispatches via
  `handler.on_event(HandlerEvent::AutoModeSwitch { ... })`, serializes
  the typed response back as `{ "response": "yes" | "yes_always" |
  "no" }`. Falls through to `No` if the handler returns an unexpected
  response variant.

Tests:

- `auto_mode_switch_dispatches_to_handler_and_serializes_response` —
  asserts the inbound `autoModeSwitch.request` reaches the typed
  handler with the expected `error_code` / `retry_after_seconds`, and
  that returning `AutoModeSwitchResponse::YesAlways` serializes as
  `{ "response": "yes_always" }` on the wire.
- `auto_mode_switch_default_handler_replies_no` — asserts the trait's
  default impl replies `{ "response": "no" }` when no override is
  provided.
- Updated `session_config_default_enables_permission_flow_flags` and
  `resume_session_config_new_enables_permission_flow_flags` to assert
  the new flag is `Some(true)` by default.

Documentation:

- README: new bullet under "Rust-only API" section.
- CHANGELOG: new entry under `### Added > Handlers + helpers`
  documenting the wire path, the typed enum, and the cross-SDK
  divergence with a note that Node / Python / Go / .NET parity is
  post-release work.
- `request_auto_mode_switch` field rustdoc cross-references
  `SessionHandler::on_auto_mode_switch`.

Validation:

- 209 tests pass (was 207 — 2 new auto-mode-switch tests).
- `cargo doc -D warnings` clean.
- `cargo +nightly-2026-04-14 fmt --check` clean.
- `cargo clippy --all-features --all-targets -- -D warnings` clean.

Migration note for the github-app sync session: the typed
`AutoModeSwitchResponse` enum replaces github-app's vendored
`AutoModeSwitchResponse { response: String }`. github-app's WebSocket
relay continues to receive the user's choice as a `String`; map to the
typed enum at the boundary
(`match s.as_str() { "yes" => Yes, "yes_always" => YesAlways, _ => No }`).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Client::list_sessions wire shape — wrap filter under params.filter

The hand-authored `Client::list_sessions` was serializing the optional
`SessionListFilter` directly onto the JSON-RPC `params` object,
flattening fields like `repository` / `branch` / `cwd` / `gitRoot` to
the top level. The `session.list` request shape that the runtime accepts
puts the filter under `params.filter` — and that's what every other SDK
sends:

- Node `nodejs/src/client.ts:1178-1180`: `sendRequest("session.list", { filter })`
- Go `go/types.go`: `listSessionsRequest { Filter *SessionListFilter }`
- Python `python/copilot/client.py:1907-1911`: `payload["filter"] = ...`
- .NET `dotnet/src/Client.cs`: `record ListSessionsRequest(SessionListFilter? Filter)`

Because the runtime silently ignores unknown top-level keys on
`session.list`, calling `list_sessions(Some(filter))` was functionally
equivalent to `list_sessions(None)` in 0.0.x — every filter field was
discarded by the runtime, returning an unfiltered session list. No
runtime error, no log, just silently broken. Functionally dead on the
wire, same class as the elicitation `requestedSchema` fix in `c58e2f2`.

The mock-server test `list_sessions_serializes_typed_filter` asserted on
the flat shape it observed (`request["params"]["repository"]`) rather
than the schema's wrapped shape, so the bug round-tripped through both
ends — the implementation produced the wrong shape, the test verified
the wrong shape. Same root cause as the elicitation test gap.

Fix:

- `Client::list_sessions` now wraps the filter: `Some(f) ->
  serde_json::json!({ "filter": f })`, `None -> serde_json::json!({})`.
  `None` omits the filter key entirely (matches Go's `omitempty`
  behavior; Node's `{ filter: undefined }` also omits via JSON-stringify).
- Mock-server test now asserts on the wrapped path
  (`params.filter.repository`, `params.filter.branch`) AND explicitly
  asserts the flattened fallback is gone (`params.get("repository")`
  must return `None`). Same regression-prevention pattern as the
  elicitation fix at `session_test.rs:1248-1251`.
- CHANGELOG entry under `### Fixed` documenting the wire-shape fix and
  the test gap that masked it.

Validation:

- 209 tests pass (no count change — same test, stricter assertions).
- `cargo doc -D warnings` clean.
- `cargo +nightly-2026-04-14 fmt --check` clean.
- `cargo clippy --all-features --all-targets -- -D warnings` clean.

Caught by the gap-analysis structural-correctness pass walking every
hand-authored `client.call("...")` site against the schema and the four
other SDKs. This is the second wire-shape bug found by that pass; the
first was the `SessionUi::elicitation` `schema` -> `requestedSchema`
fix in `c58e2f2`. The audit confirms `session.list` is the only other
new bug — three Rust-unique surfaces (`session.respondToUserInput`,
`session.sendTelemetry`, top-level `sendTelemetry` /
`server.sendTelemetry`) are uncheckable cross-SDK and queued as
post-0.1.0 runtime-acceptance verification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Bump @github/copilot pin to ^1.0.39 + regen Rust types

Per the auto-mode-switch CLI hunt session's verification, the
`@github/copilot@1.0.39-0` pin we were on does NOT contain
copilot-agent-runtime PR #7024 (auto-mode-switch wire support).
The runtime PR merged after `1.0.39-0` was cut as a release-candidate
and shipped in `1.0.39` final, with `1.0.40-0` after.

This commit:

- Bumps `nodejs/package.json` from `^1.0.39-0` to `^1.0.39` (final).
  `npm install` resolved to `1.0.39`. The package-lock.json is
  regenerated accordingly.
- Regenerates Rust t…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants