Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9e7b57a
Close Language Gaps for Commands + Dialogs/Elicitations
MRayermannMSFT Mar 31, 2026
753eb2c
Fix code quality review feedback and formatting issues
MRayermannMSFT Mar 31, 2026
1a5c4ee
Fix Python ruff lint errors: unused imports, import sort order, line …
MRayermannMSFT Mar 31, 2026
d9568d1
Fix Python type checker errors: remove unused type-ignore comments, f…
MRayermannMSFT Mar 31, 2026
51fea3d
Fix Go struct field alignment for go fmt compliance
MRayermannMSFT Mar 31, 2026
b93c2bf
Fix Python E2E tests: use correct snapshot directory 'multi_client'
MRayermannMSFT Mar 31, 2026
da308cc
Skip flaky Python E2E disconnect test: force_stop() doesn't trigger c…
MRayermannMSFT Mar 31, 2026
a70db0d
fix: close makefile wrapper in Python force_stop() to trigger TCP dis…
MRayermannMSFT Mar 31, 2026
4b92b83
fix: address remaining code review feedback
MRayermannMSFT Mar 31, 2026
f64f8f3
fix: use socket.shutdown() in Python force_stop() for reliable discon…
MRayermannMSFT Mar 31, 2026
2e11ed7
chore: remove working markdown files from PR
MRayermannMSFT Mar 31, 2026
34da394
fix: pass full elicitation schema in Go, add schema tests across SDKs
MRayermannMSFT Mar 31, 2026
5f58ed4
fix: Go test compilation errors for schema extraction test
MRayermannMSFT Mar 31, 2026
a66e0c7
fix: resolve staticcheck SA4031 lint in Go schema test
MRayermannMSFT Mar 31, 2026
7553af6
test: add Go command error, unknown command, and elicitation handler …
MRayermannMSFT Mar 31, 2026
8035e3c
fix: remove redundant nil check flagged by staticcheck SA4031
MRayermannMSFT Mar 31, 2026
259fbd5
docs: promote Commands and UI Elicitation to top-level sections in .N…
MRayermannMSFT Mar 31, 2026
bce4b2d
fix: address human review feedback
MRayermannMSFT Apr 1, 2026
3c2abae
refactor: merge ElicitationRequest + ElicitationInvocation into Elici…
MRayermannMSFT Apr 2, 2026
4a51cd2
refactor: apply ElicitationContext rename to Node.js SDK
MRayermannMSFT Apr 2, 2026
3518f99
style: fix formatting (prettier, ruff, trailing newlines)
MRayermannMSFT Apr 2, 2026
dd594c0
style: fix Python import sort order in __init__.py
MRayermannMSFT Apr 2, 2026
d5fd57d
fix: simplify Ui auto-property and remove empty snapshot files
MRayermannMSFT Apr 2, 2026
afc1937
fix: rename misleading command test names
MRayermannMSFT Apr 2, 2026
c191b4d
fix: remove leftover JSDoc from ElicitationRequest rename
MRayermannMSFT Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: pass full elicitation schema in Go, add schema tests across SDKs
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.
  • Loading branch information
MRayermannMSFT committed Apr 2, 2026
commit 34da39409ba7124f37af4f2f0fcd63141f167db6
8 changes: 7 additions & 1 deletion go/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,13 @@ func (s *Session) handleBroadcastEvent(event SessionEvent) {
}
var requestedSchema map[string]any
if event.Data.RequestedSchema != nil {
requestedSchema = event.Data.RequestedSchema.Properties
requestedSchema = map[string]any{
"type": string(event.Data.RequestedSchema.Type),
"properties": event.Data.RequestedSchema.Properties,
}
if len(event.Data.RequestedSchema.Required) > 0 {
requestedSchema["required"] = event.Data.RequestedSchema.Required
}
}
mode := ""
if event.Data.Mode != nil {
Expand Down
66 changes: 56 additions & 10 deletions go/session_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package copilot

import (
"strings"
"sync"
"sync/atomic"
"testing"
Expand Down Expand Up @@ -344,7 +345,7 @@ func TestSession_ElicitationCapabilityGating(t *testing.T) {
t.Fatal("Expected error when elicitation capability is missing")
}
expected := "elicitation is not supported"
if !containsString(err.Error(), expected) {
if !strings.Contains(err.Error(), expected) {
t.Errorf("Expected error to contain %q, got %q", expected, err.Error())
}
})
Expand Down Expand Up @@ -382,15 +383,60 @@ func TestSession_ElicitationHandler(t *testing.T) {
})
}

func containsString(s, substr string) bool {
return len(s) >= len(substr) && searchSubstring(s, substr)
}
func TestSession_ElicitationRequestSchema(t *testing.T) {
t.Run("elicitation.requested passes full schema to handler", func(t *testing.T) {
session, cleanup := newTestSession()
defer cleanup()

func searchSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
session.setCapabilities(&SessionCapabilities{
UI: &UICapabilities{Elicitation: true},
})

var receivedSchema map[string]any
session.registerElicitationHandler(func(req ElicitationRequest, inv ElicitationInvocation) (ElicitationResult, error) {
receivedSchema = req.RequestedSchema
return ElicitationResult{Action: "cancel"}, nil
})

// Build a synthetic elicitation.requested event with type, properties, and required
schemaType := RequestedSchemaType("object")
required := []string{"name", "age"}
event := SessionEvent{
Type: SessionEventTypeElicitationRequested,
Data: SessionEventData{
RequestID: String("req-1"),
Message: String("Fill in your info"),
RequestedSchema: &RequestedSchema{
Type: schemaType,
Properties: map[string]any{
"name": map[string]any{"type": "string"},
"age": map[string]any{"type": "number"},
},
Required: required,
},
},
}
}
return false

session.handleEvent(event)
// Give the event loop time to dispatch
time.Sleep(50 * time.Millisecond)

if receivedSchema == nil {
t.Fatal("Expected handler to receive schema, got nil")
}
if receivedSchema["type"] != "object" {
t.Errorf("Expected schema type 'object', got %v", receivedSchema["type"])
}
props, ok := receivedSchema["properties"].(map[string]any)
if !ok || props == nil {
t.Fatal("Expected schema properties map")
}
if len(props) != 2 {
t.Errorf("Expected 2 properties, got %d", len(props))
}
req, ok := receivedSchema["required"].([]string)
if !ok || len(req) != 2 {
t.Errorf("Expected required [name, age], got %v", receivedSchema["required"])
}
})
}
70 changes: 70 additions & 0 deletions python/test_commands_and_elicitation.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,76 @@ async def mock_request(method, params):
finally:
await client.force_stop()

@pytest.mark.asyncio
async def test_elicitation_handler_receives_full_schema(self):
"""Verifies that requestedSchema passes type, properties, and required to handler."""
client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
await client.start()

try:
handler_calls: list = []

async def elicitation_handler(
request: ElicitationRequest, invocation: dict[str, str]
) -> ElicitationResult:
handler_calls.append(request)
return {"action": "cancel"}

session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
on_elicitation_request=elicitation_handler,
)

original_request = client._client.request

async def mock_request(method, params):
if method == "session.ui.handlePendingElicitation":
return {"success": True}
return await original_request(method, params)

client._client.request = mock_request

from copilot.generated.session_events import (
Data,
RequestedSchema,
RequestedSchemaType,
SessionEvent,
SessionEventType,
)

event = SessionEvent(
data=Data(
request_id="req-schema-1",
message="Fill in your details",
requested_schema=RequestedSchema(
type=RequestedSchemaType.OBJECT,
properties={
"name": {"type": "string"},
"age": {"type": "number"},
},
required=["name", "age"],
),
),
id="evt-schema-1",
timestamp="2025-01-01T00:00:00Z",
type=SessionEventType.ELICITATION_REQUESTED,
ephemeral=True,
parent_id=None,
)
session._dispatch_event(event)

await asyncio.sleep(0.2)

assert len(handler_calls) == 1
schema = handler_calls[0].get("requestedSchema")
assert schema is not None, "Expected requestedSchema in handler call"
assert schema["type"] == "object"
assert "name" in schema["properties"]
assert "age" in schema["properties"]
assert schema["required"] == ["name", "age"]
finally:
await client.force_stop()


# ============================================================================
# Capabilities changed event
Expand Down