Skip to content

dotnet: add CreateCloudSessionAsync#1399

Closed
tclem wants to merge 2 commits into
tclem/verbose-memefrom
tclem/dotnet-create-cloud-session
Closed

dotnet: add CreateCloudSessionAsync#1399
tclem wants to merge 2 commits into
tclem/verbose-memefrom
tclem/dotnet-create-cloud-session

Conversation

@tclem
Copy link
Copy Markdown
Member

@tclem tclem commented May 23, 2026

Stacked on #1394 — will be retargeted to main when that merges.

Ports CreateCloudSessionAsync to the .NET SDK, following the Rust (#1394) and TypeScript (#1395) implementations as the reference contract.

What changes

CreateCloudSessionAsync(CloudSessionConfig config) — new method on CopilotClient:

  • Sends session.create without a caller-supplied sessionId; the cloud server assigns one.
  • Sets session.SessionId and session.RemoteUrl (new CopilotSession property) from the server response.
  • CreateSessionAsync now throws ArgumentException if a Cloud block is present, preventing accidental misconfiguration.

Pending-routing buffer — handles the window between sending session.create and receiving the server's response:

  • Incoming session.event notifications for the not-yet-registered session ID are buffered per-session (bounded at 128, drop-oldest) and replayed atomically when the session is registered via FlushPendingForSession.
  • Inbound RPC requests (userInput.request, permission.request, hooks, etc.) park on a TaskCompletionSource in ResolveSessionAsync and are unblocked when the session is registered. _pendingRoutingCount tracks how many cloud-create calls are in-flight; when all guards are released without registration, parked waiters are faulted.
  • BeginPendingSessionRouting / EndPendingSessionRouting / PendingSessionRoutingGuard manage the lifetime. All five existing RpcHandler inbound request handlers now call ResolveSessionAsync instead of GetSession.

Tests — 9 new unit tests in CloudSessionTests.cs:

  • Serialization: null sessionId is omitted from wire JSON; CloudSessionOptions round-trips correctly.
  • Guard: CreateSessionAsync rejects cloud config; CreateCloudSessionAsync requires cloud config and rejects caller-supplied IDs and provider overrides.
  • Wire: emitted session.create omits sessionId and includes cloud block; session.SessionId and RemoteUrl are populated from the response.
  • Buffer: early session.event notifications are replayed after registration.
  • Parking: inbound userInput.request arriving before registration is buffered and answered after the session is registered.

  Generated via Copilot (Claude Sonnet 4.6) on behalf of @tclem

Port CreateCloudSessionAsync from the Rust (PR #1394) and TypeScript (PR #1395) SDKs.

- CreateCloudSessionAsync sends session.create without a caller-supplied sessionId, so
  the cloud server assigns one. The server-assigned sessionId is set on the returned
  session object alongside a new RemoteUrl property.
- CreateSessionAsync now rejects configs that include a Cloud block; callers must use
  CreateCloudSessionAsync for cloud sessions.
- A pending-routing buffer handles the window between sending session.create and receiving
  the response: incoming session.event notifications are buffered per-sessionId (bounded at
  128 with drop-oldest semantics) and replayed once the session is registered; inbound
  RPC requests (userInput.request, permission.request, etc.) park on a TaskCompletionSource
  and are unblocked atomically when the session is registered.
- Added RemoteUrl property to CopilotSession.
- 9 unit tests cover the core behaviours: serialization, guard/buffer mechanics, early
  notification replay, and inbound request parking.

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

This comment has been minimized.

Carries forward the Rust SDK PR #1394 follow-up review feedback into the
.NET port:

1. Cap the per-session inbound-request parked-waiter list at 128. When
   exceeded, reject the oldest waiter with 'pending session buffer
   overflow'. The custom JsonRpc dispatcher translates the thrown
   exception into a JSON-RPC error response (-32603) back to the runtime
   so the request id isn't left hanging — silently dropping it would
   leave the runtime waiting on the response until its own timeout.
   Mirrors Rust commit 491b442 and TS commit c167bc3.

2. Use a distinct message ('pending session routing ended before
   session was registered') when the pending guard drops without
   registration. Lets debugging tell the overflow path from the
   create-failed path. Also fixes the pre-existing bug where the
   waiter-fault loop ran inside _pendingLock despite the comment saying
   otherwise — moved it outside the lock so TCS continuations can't
   deadlock against the lock. Mirrors Rust commit e0ff254.

Adds two tests:
- overflow path: 129 early inbound requests → oldest gets error with
  'pending session buffer overflow', remaining 128 resolve after
  registration
- guard-drop path: session.create fails with parked request → error
  response with 'pending session routing ended before session was
  registered'

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

Cross-SDK Consistency Review 🔍

This PR correctly adds CreateCloudSessionAsync to the .NET SDK, following the Rust (#1394) and TypeScript (#1395) reference implementations. The implementation looks consistent with those — dedicated method, server-assigned session ID, RemoteUrl on the session object, pending-routing buffer, and guard on CreateSessionAsync rejecting cloud config.

Current state across SDKs

SDK Status
Rust CreateCloudSession (#1394, reference)
TypeScript createCloudSession (#1395, open)
.NET CreateCloudSessionAsync (this PR)
Python ⚠️ Missing dedicated create_cloud_session — only has cloud param on create_session
Go ⚠️ Missing dedicated CreateCloudSession — only has Cloud field on CreateSession config

What Python and Go are missing

Both Python and Go will need follow-up PRs to reach parity:

  • A dedicated create_cloud_session / CreateCloudSession method that omits sessionId from the wire payload and accepts the server-assigned ID
  • remote_url / RemoteUrl property on the session object
  • Guard on create_session / CreateSession rejecting cloud config (same pattern as Rust/TS/.NET)
  • Pending-routing buffer for early session.event notifications

No blocking issues with this PR itself — the .NET implementation is consistent with the established pattern. Just flagging the remaining gap so follow-up work can be tracked.

Generated by SDK Consistency Review Agent for issue #1399 · ● 8.6M ·

@tclem
Copy link
Copy Markdown
Member Author

tclem commented May 23, 2026

Consolidating into #1395 so the sdk-consistency-review workflow sees the full cross-SDK matrix in one PR. Commits from this branch are merged into that PR's branch.

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

@tclem tclem closed this May 23, 2026
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.

1 participant