Skip to content

feat(rust): route cloud sessions through create_session#1445

Open
chuwik wants to merge 10 commits into
mainfrom
chuwik/cloud-session-merge
Open

feat(rust): route cloud sessions through create_session#1445
chuwik wants to merge 10 commits into
mainfrom
chuwik/cloud-session-merge

Conversation

@chuwik
Copy link
Copy Markdown

@chuwik chuwik commented May 27, 2026

Summary

  • Adds Rust cloud session config and wire support via SessionConfig::with_cloud.
  • Routes cloud configs through Client::create_session while keeping cloud creation as an internal helper.
  • Uses the runtime-assigned cloud session ID returned by session.create for registration and session state.

Context

Builds on #1394 while preserving the existing public Client::create_session API.

tclem and others added 8 commits May 22, 2026 18:53
Cloud (Mission Control) sessions don't fit the create_session contract:
the runtime owns the session id, forbids caller-provided sessionId and
provider, and may emit session.event notifications or inbound JSON-RPC
requests before the session.create response.

Add a dedicated Client::create_cloud_session entry point that:

- Omits sessionId from the session.create wire (new
  SessionConfig::into_cloud_wire via shared into_create_wire(Option)).
- Rejects caller-provided session_id and provider with InvalidConfig.
- Buffers early session-keyed notifications and inbound requests through
  a refcounted PendingSessionRouting guard with a bounded (128)
  drop-oldest queue, replayed when register_session installs channels
  for the runtime-assigned id.
- Holds the guard across a best-effort session.destroy on decode
  failure, then drops it.

Also:

- Client::create_session now rejects config.cloud with InvalidConfig,
  pointing callers at create_cloud_session.
- Factor the shared handler/sessionFs extraction into
  prepare_session_runtime, reused by create / create_cloud / resume.
- Five router unit tests cover off-mode drop, on-mode buffer + ordered
  flush, drop-oldest at limit, last-guard-drop clears, and
  unregister-clears-pending.
- Six integration tests cover the cloud-create wire, both rejection
  paths, handler-driven request flags, and early-notification +
  early-request buffering, against the existing fake JSON-RPC server.

Ports github/github-app#5592 into the canonical SDK so the app can drop
its vendored CloudBootstrapWire hack on the next refresh.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tokio tasks are cheap; the lazy ensure_started + idempotency guard
existed only because cloud session creation needed the router running
before any session was registered. Starting both routing tasks once
during Client::from_streams collapses the lazy/eager split and lets
register_session and begin_pending_session_routing become plain
delegations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously the pending router buffered notifications and inbound JSON-RPC
requests in two separate VecDeques and on register flushed all
notifications, then all requests. That meant a request arriving between
two notifications would be delivered to the consumer after both
notifications instead of in arrival order, batching cross-type messages
in a way the docs claimed not to. Some downstream behaviors care: an
'approval requested' session event observed before its matching
'tool.call' request keeps the consumer's permission/elicitation flow
coherent.

Collapse the two queues into a single FIFO of PendingItem (a notif/req
enum), apply the drop-oldest limit globally, and flush in arrival order.
Adds a regression test that interleaves a request between two
notifications and verifies cross-type ordering survives the flush.

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

When the pending session buffer overflows during cloud session creation,
we still have to evict the oldest entry to make room. For notifications
that's fine; for inbound JSON-RPC requests it left the runtime hanging
on a reply that would never come (until the runtime's own timeout).

Add a sync WriterHandle on JsonRpcClient that lets the router enqueue
fire-and-forget frames from inside its parking_lot::Mutex. On request
eviction, send a JSON-RPC error response (-32603 INTERNAL_ERROR,
message 'request dropped: pending session buffer overflow before
session.create response') for the evicted request's id before discarding
it. Notifications continue to drop with a warn-level log.

Test reads bytes back off a duplex stream and asserts the error
response is well-formed and carries the expected id and code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed before tests because nightly rustfmt reordered imports and wrapped a router test call differently than the committed source. Running the pinned formatter produced the expected import grouping and line wrapping without changing behavior.

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

GPT-5.5 self-review surfaced two issues in the pending-session router:

1. When the last PendingSessionRouting guard dropped without anyone
   calling register_session (e.g. session.create failed mid-RPC), any
   buffered inbound JSON-RPC requests were silently discarded. The
   runtime is waiting on those ids and would hang until its own timeout.
   This was a symmetric gap to the overflow path fixed in 491b442.

   Fix: Drop impl now drains pending entries and emits an INTERNAL_ERROR
   response ("pending session routing ended before session was
   registered") for every PendingItem::Request before clearing.
   Notifications still just warn-log on the way out. WriterHandle is
   now Clone so the snapshot can be taken under lock and the lock
   released before per-item work.

2. The 'cross-type arrival order' claim from c802943 overstated what
   the unified FIFO actually guarantees. After register, items still
   flow through two separate per-session mpsc channels consumed via
   select! in session.rs, so consumer-observed cross-type order is
   implementation-defined. Updated push_pending docs to describe the
   actual guarantee (FIFO eviction + interleaved flush) and renamed
   the test from pending_buffer_preserves_cross_type_arrival_order to
   pending_buffer_flush_interleaves_types_in_arrival_order. Unifying
   per-session channels into one stream is tracked as a follow-up.

New router test: last_guard_drop_without_register_responds_to_buffered_requests.
8 router tests + 101 session tests all pass; clippy clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	rust/src/session.rs
#	rust/tests/session_test.rs
Start from the Rust cloud session support in PR #1394 and keep the public session creation API on Client::create_session. Cloud configs now delegate to a private cloud creation path that preserves pending routing for runtime-assigned session IDs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@chuwik chuwik marked this pull request as ready for review May 27, 2026 05:06
@chuwik chuwik requested a review from a team as a code owner May 27, 2026 05:06
Copilot AI review requested due to automatic review settings May 27, 2026 05:06
@chuwik chuwik requested a review from tclem May 27, 2026 05:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Rust SDK so cloud-backed sessions are created through the existing Client::create_session API while preserving runtime-assigned session IDs and buffering early runtime traffic.

Changes:

  • Adds cloud-session routing in create_session, including validation, cloud wire payloads without sessionId, and runtime-assigned ID registration.
  • Refactors session runtime preparation shared by create/resume paths.
  • Extends the router to buffer early cloud notifications/requests and respond to dropped pending requests.
Show a summary per file
File Description
rust/src/session.rs Routes cloud configs through internal cloud creation and shares runtime preparation logic.
rust/src/router.rs Adds pending-session buffering, overflow handling, and router tests.
rust/src/lib.rs Starts the session router during client transport setup and exposes pending routing internally.
rust/src/jsonrpc.rs Adds a fire-and-forget writer handle for router-generated JSON-RPC error responses.
rust/src/types.rs Adds cloud-session documentation and shared local/cloud wire conversion.
rust/src/wire.rs Makes sessionId optional on session.create wire payloads.
rust/tests/session_test.rs Adds integration coverage for cloud create wire shape, validation, handler flags, and early buffering.
rust/README.md Documents cloud sessions through Client::create_session.

Copilot's findings

  • Files reviewed: 8/8 changed files
  • Comments generated: 1

Comment thread rust/src/session.rs Outdated
Comment on lines +990 to +1005
let recovered_session_id = result
.get("sessionId")
.and_then(|value| value.as_str())
.map(SessionId::from);
let create_result: CreateSessionResult = match serde_json::from_value(result) {
Ok(result) => result,
Err(error) => {
// Keep the pending guard alive across the destroy so any
// straggler events for the runtime-assigned id are still
// routed (and then dropped on guard release).
if let Some(recovered_id) = recovered_session_id {
if let Err(destroy_err) = self
.call(
"session.destroy",
Some(serde_json::json!({ "sessionId": recovered_id })),
)
chuwik and others added 2 commits May 26, 2026 22:37
Remove the pending-routing buffer for cloud session creation. Cloud sessions now register after the runtime returns the session id, and any earlier unknown-session traffic is dropped by the normal router path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Collapse local and cloud session creation onto shared request preparation, event-loop startup, and Session construction helpers so cloud support does not duplicate the normal create path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

3 participants