Skip to content

Close Language Gaps for Commands + Dialogs/Elicitations#960

Merged
MRayermannMSFT merged 25 commits intomainfrom
mrayermannmsft/uiandcommandsgaps
Apr 2, 2026
Merged

Close Language Gaps for Commands + Dialogs/Elicitations#960
MRayermannMSFT merged 25 commits intomainfrom
mrayermannmsft/uiandcommandsgaps

Conversation

@MRayermannMSFT
Copy link
Copy Markdown
Contributor

@MRayermannMSFT MRayermannMSFT commented Mar 31, 2026

What

Adds Commands and UI Elicitation support to the Python, Go, and .NET SDKs, closing the feature gap with the Node.js reference implementation. Each SDK now supports registering slash commands, handling elicitation requests from the server, and driving interactive form dialogs (confirm, select, input) through a capability-gated UI API — with full documentation, unit tests, and E2E tests matching the Node.js test surface.

Why

These two features were recently shipped in the Node.js SDK only, leaving the other three languages unable to participate in command dispatch or elicitation flows. Any multi-language deployment that relied on commands or form-based user input would have been forced to use the Node.js SDK exclusively. Closing this gap ensures all four SDKs offer the same capabilities, preventing fragmentation and letting teams choose the language that fits their stack.

⚠️ Breaking Change (Node.js only)

The Node.js ElicitationHandler signature has changed from two arguments to one. The separate ElicitationRequest and { sessionId } invocation types have been merged into a single ElicitationContext type that includes sessionId alongside the request fields. This mirrors the existing CommandContext single-argument pattern.

Before:

onElicitationRequest: async (request, invocation) => {
    console.log(invocation.sessionId, request.message);
}

After:

onElicitationRequest: async (context) => {
    console.log(context.sessionId, context.message);
}

Python, Go, and .NET are not affected — Commands and Elicitation are new APIs in those SDKs and ship with the ElicitationContext single-argument pattern from the start.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@MRayermannMSFT MRayermannMSFT marked this pull request as ready for review March 31, 2026 20:04
@MRayermannMSFT MRayermannMSFT requested a review from a team as a code owner March 31, 2026 20:04
Copilot AI review requested due to automatic review settings March 31, 2026 20:04
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 closes feature parity gaps across the SDKs by adding slash commands and UI elicitation dialogs / elicitation callbacks, including capability reporting and multi-client broadcast behavior (capabilities/commands changed) with corresponding docs and tests.

Changes:

  • Adds command registration + command.execute dispatch/response handling across Python, Go, and .NET.
  • Adds UI elicitation APIs (confirm/select/input/custom schema), onElicitationRequest callback handling, and capabilities plumbing (create/resume response + capabilities.changed).
  • Adds/updates E2E + unit tests and replay snapshots to cover single- and multi-client scenarios.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/snapshots/multi_client/client_receives_commands_changed_when_another_client_joins_with_commands.yaml New replay snapshot for multi-client commands broadcast scenario
test/snapshots/multi_client/capabilities_changed_fires_when_second_client_joins_with_elicitation_handler.yaml New replay snapshot for multi-client capabilities enablement scenario
test/snapshots/multi_client/capabilities_changed_fires_when_elicitation_provider_disconnects.yaml New replay snapshot for multi-client capabilities disablement scenario
test/snapshots/elicitation/session_without_elicitationhandler_reports_no_capability.yaml New replay snapshot for elicitation capability baseline
test/snapshots/elicitation/session_without_elicitationhandler_creates_successfully.yaml New replay snapshot for session creation without elicitation handler
test/snapshots/elicitation/session_without_elicitation_handler_reports_no_capability.yaml New replay snapshot variant for elicitation capability baseline
test/snapshots/elicitation/session_with_elicitationhandler_reports_elicitation_capability.yaml New replay snapshot for elicitation capability when handler is present
test/snapshots/elicitation/session_with_elicitation_handler_reports_elicitation_capability.yaml New replay snapshot variant for elicitation capability when handler is present
test/snapshots/elicitation/sends_requestelicitation_when_handler_provided.yaml New replay snapshot for requestElicitation flag behavior
test/snapshots/elicitation/elicitation_throws_when_capability_is_missing.yaml New replay snapshot for capability-gated UI API behavior
test/snapshots/elicitation/defaults_capabilities_when_not_provided.yaml New replay snapshot for default capabilities behavior
test/snapshots/commands/session_with_no_commands_creates_successfully.yaml New replay snapshot for session creation with no commands
test/snapshots/commands/forwards_commands_in_session_resume.yaml New replay snapshot for resume payload forwarding commands
test/snapshots/commands/forwards_commands_in_session_create.yaml New replay snapshot for create payload forwarding commands
python/test_commands_and_elicitation.py New Python unit coverage for commands, elicitation APIs/callbacks, and capabilities.changed
python/README.md Documents Python commands + UI elicitation + elicitation request handler semantics
python/e2e/test_ui_elicitation.py Python E2E tests for capability gating + capability reporting with handler
python/e2e/test_ui_elicitation_multi_client.py Python E2E multi-client tests for capabilities.changed enable/disable behavior
python/e2e/test_commands.py Python E2E multi-client test for commands.changed broadcast
python/copilot/session.py Implements Python command routing, UI elicitation API, elicitation callback handling, and capabilities updates
python/copilot/client.py Wires commands + requestElicitation flags into create/resume payloads; stores capabilities from responses; improves TCP socket termination
python/copilot/init.py Exposes newly added Python public types (commands/elicitation/capabilities/UI API)
go/types.go Adds Go commands/elicitation/capabilities types and a new Int() pointer helper
go/session.go Implements Go command routing, UI elicitation API, elicitation callback handling, and capabilities storage
go/session_test.go Adds Go unit tests for command routing + capabilities + elicitation gating/handler storage
go/client.go Serializes commands + requestElicitation and registers handlers; stores capabilities from responses
go/client_test.go Adds Go request/response serialization tests for commands + requestElicitation + capabilities
go/README.md Documents Go commands + UI elicitation + elicitation request handling
go/internal/e2e/commands_and_elicitation_test.go Adds Go E2E coverage for commands + elicitation + multi-client capabilities changes
dotnet/test/MultiClientCommandsElicitationTests.cs Adds .NET E2E multi-client tests for commands.changed and capabilities.changed enable/disable
dotnet/test/ElicitationTests.cs Adds .NET E2E + type-structure tests for elicitation + capabilities gating
dotnet/test/CommandsTests.cs Adds .NET E2E + type-structure tests for commands + config cloning
dotnet/src/Types.cs Adds .NET public types for commands, elicitation, UI API, and capabilities; extends config cloning
dotnet/src/Session.cs Implements .NET command routing, elicitation callback handling, UI API, and capabilities updates
dotnet/src/Client.cs Adds commands + requestElicitation to wire requests; stores capabilities from responses; adds JSON source-gen types
dotnet/README.md Documents .NET commands + UI elicitation + elicitation request handler behavior

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@MRayermannMSFT MRayermannMSFT force-pushed the mrayermannmsft/uiandcommandsgaps branch from 1d0a155 to 0c4ff33 Compare April 1, 2026 23:25
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

MackinnonBuck
MackinnonBuck previously approved these changes Apr 2, 2026
Copy link
Copy Markdown
Collaborator

@MackinnonBuck MackinnonBuck left a comment

Choose a reason for hiding this comment

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

Looks great! Just a minor nit.

@github-actions

This comment has been minimized.

…apabilities.changed reliably in replay proxy
…connect

Python's socket.makefile() holds its own reference to the socket.
Calling socket.close() alone won't release the OS-level resource
until the makefile wrapper is also closed. This meant force_stop()
wasn't actually closing the TCP connection, so the server never
detected the disconnect and never sent capabilities.changed events
to other clients.

Fix: close the file wrapper before the socket in SocketWrapper.terminate().
Unskip test_capabilities_changed_when_elicitation_provider_disconnects.
- Narrow generic catch clauses in .NET command/elicitation handlers
  with 'when (ex is not OperationCanceledException)' filter
- Remove redundant null-conditional (val?.ToString -> val.ToString)
  in SelectAsync and InputAsync switch expressions
- Add explanatory comments to Python empty except blocks
…nect

socket.close() and file wrapper close don't reliably interrupt a
blocking readline() on another thread in Python. socket.shutdown(SHUT_RDWR)
sends TCP FIN to the server immediately (triggering server-side
disconnect detection) and interrupts any pending blocking reads
across threads — matching Node.js socket.destroy() and Go conn.Close()
behavior.
Go was only passing RequestedSchema.Properties to the elicitation
handler, dropping the 'type' and 'required' fields. This meant
handlers couldn't reconstruct the full JSON Schema. Now passes a
complete map with type, properties, and required.

Also replaces custom containsString/searchSubstring helpers in Go
tests with strings.Contains, and adds tests in Go and Python that
verify the full schema is passed through to elicitation handlers.
Use direct schema extraction logic test instead of dispatching
through session event machinery, avoiding need for RPC mocks.
Fixes undefined SessionEventData and handleEvent references.
…tests

- Command handler error propagation: verifies handler error is returned
- Unknown command: verifies getCommandHandler returns false for unknown
- Elicitation handler error: verifies error propagation from handler
- Elicitation handler success: verifies result with action and content
…ET README

Matches Go and Python README structure where these are ## (h2) sections
rather than ### (h3) subsections. Closes documentation gap flagged
by SDK Consistency Review Agent.
.NET:
- Cache SessionUiApiImpl instance via Lazy<> instead of allocating on
  every .Ui access
- Await command/elicitation handler calls instead of redundant
  fire-and-forget (outer caller already fire-and-forgets)
- Use ElicitationRequestedDataMode enum for Mode instead of string

Go:
- Handle SessionEventTypeCapabilitiesChanged in handleBroadcastEvent
  to update session capabilities when other clients join/leave with
  elicitation handlers
- Add test verifying capabilities.changed event updates session
…tationContext

Combines the two-argument elicitation handler pattern into a single
ElicitationContext type across all three SDKs, matching the existing
CommandContext pattern. The context now includes SessionId alongside
the request fields (Message, RequestedSchema, Mode, etc.).

Changes per language:
- .NET: ElicitationContext class, single-arg delegate, Lazy<> cached Ui
- Go: ElicitationContext struct, single-arg handler func
- Python: ElicitationContext TypedDict, single-arg callable

All tests, READMEs, and E2E tests updated.
Consistent with Python, Go, and .NET — ElicitationRequest is now
ElicitationContext with sessionId included. Handler takes single arg.
Completes the cross-SDK consistency change.
@MRayermannMSFT MRayermannMSFT force-pushed the mrayermannmsft/uiandcommandsgaps branch from 255b375 to dd594c0 Compare April 2, 2026 15:49
- Replace Lazy<ISessionUiApi> with simple auto-property initialized
  in constructor, per reviewer feedback
- Delete 14 empty snapshot YAML files (commands, elicitation,
  multi_client) that had no conversation data
Renamed to accurately reflect what they verify:
- Forwards_Commands_In_Session_Create -> Session_With_Commands_Creates_Successfully
- Forwards_Commands_In_Session_Resume -> Session_With_Commands_Resumes_Successfully

Actual forwarding verification is in the multi-client test
Client_Receives_Commands_Changed_When_Another_Client_Joins_With_Commands
which proves the server received the commands by checking the
commands.changed event on another client.
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

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 #960

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Cross-SDK Consistency Review ✅ (with one minor finding)

This PR does an excellent job closing the Commands + UI Elicitation feature gap across all four SDKs. The API surfaces are well-aligned:

Feature Node.js Python Go .NET
commands config commands commands Commands Commands
Elicitation handler onElicitationRequest on_elicitation_request OnElicitationRequest OnElicitationRequest
UI API session.ui session.ui session.UI() session.Ui
Capabilities session.capabilities session.capabilities session.Capabilities() session.Capabilities
ElicitationContext single-arg ✓ single-arg ✓ single-arg ✓ single-arg ✓

The Node.js ElicitationHandler breaking change (two-arg → single ElicitationContext) correctly aligns all four SDKs to the same CommandContext single-argument pattern — a nice consistency improvement.

One Python naming inconsistency flagged

The only cross-language consistency issue found is in the Python SDK's public API types, where several new TypedDicts use camelCase field names instead of Python-conventional snake_case:

  • ElicitationContext.requestedSchema / .elicitationSource → should be requested_schema / elicitation_source
  • InputOptions.minLength / .maxLength → should be min_length / max_length
  • ElicitationParams.requestedSchema → should be requested_schema

The parallel CommandContext dataclass in this same PR uses all snake_case (session_id, command_name), and the existing SDK TypedDicts (UserInputRequest, UserInputInvocation) also use snake_case. These camelCase fields appear to have leaked from the JSON wire format into the user-facing Python API. See inline comments for details.

Everything else — wire protocol, session lifecycle integration, event routing, capability gating, and test coverage — looks well-implemented and consistent across all four SDKs.

Generated by SDK Consistency Review Agent for issue #960 ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

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 #960

@MRayermannMSFT MRayermannMSFT added this pull request to the merge queue Apr 2, 2026
Merged via the queue into main with commit dd42d42 Apr 2, 2026
35 checks passed
@MRayermannMSFT MRayermannMSFT deleted the mrayermannmsft/uiandcommandsgaps branch April 2, 2026 20:39
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