Skip to content

Commit ec612a4

Browse files
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.
1 parent c78a485 commit ec612a4

3 files changed

Lines changed: 133 additions & 11 deletions

File tree

go/session.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,13 @@ func (s *Session) handleBroadcastEvent(event SessionEvent) {
956956
}
957957
var requestedSchema map[string]any
958958
if event.Data.RequestedSchema != nil {
959-
requestedSchema = event.Data.RequestedSchema.Properties
959+
requestedSchema = map[string]any{
960+
"type": string(event.Data.RequestedSchema.Type),
961+
"properties": event.Data.RequestedSchema.Properties,
962+
}
963+
if len(event.Data.RequestedSchema.Required) > 0 {
964+
requestedSchema["required"] = event.Data.RequestedSchema.Required
965+
}
960966
}
961967
mode := ""
962968
if event.Data.Mode != nil {

go/session_test.go

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package copilot
22

33
import (
4+
"strings"
45
"sync"
56
"sync/atomic"
67
"testing"
@@ -344,7 +345,7 @@ func TestSession_ElicitationCapabilityGating(t *testing.T) {
344345
t.Fatal("Expected error when elicitation capability is missing")
345346
}
346347
expected := "elicitation is not supported"
347-
if !containsString(err.Error(), expected) {
348+
if !strings.Contains(err.Error(), expected) {
348349
t.Errorf("Expected error to contain %q, got %q", expected, err.Error())
349350
}
350351
})
@@ -382,15 +383,60 @@ func TestSession_ElicitationHandler(t *testing.T) {
382383
})
383384
}
384385

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

389-
func searchSubstring(s, substr string) bool {
390-
for i := 0; i <= len(s)-len(substr); i++ {
391-
if s[i:i+len(substr)] == substr {
392-
return true
391+
session.setCapabilities(&SessionCapabilities{
392+
UI: &UICapabilities{Elicitation: true},
393+
})
394+
395+
var receivedSchema map[string]any
396+
session.registerElicitationHandler(func(req ElicitationRequest, inv ElicitationInvocation) (ElicitationResult, error) {
397+
receivedSchema = req.RequestedSchema
398+
return ElicitationResult{Action: "cancel"}, nil
399+
})
400+
401+
// Build a synthetic elicitation.requested event with type, properties, and required
402+
schemaType := RequestedSchemaType("object")
403+
required := []string{"name", "age"}
404+
event := SessionEvent{
405+
Type: SessionEventTypeElicitationRequested,
406+
Data: SessionEventData{
407+
RequestID: String("req-1"),
408+
Message: String("Fill in your info"),
409+
RequestedSchema: &RequestedSchema{
410+
Type: schemaType,
411+
Properties: map[string]any{
412+
"name": map[string]any{"type": "string"},
413+
"age": map[string]any{"type": "number"},
414+
},
415+
Required: required,
416+
},
417+
},
393418
}
394-
}
395-
return false
419+
420+
session.handleEvent(event)
421+
// Give the event loop time to dispatch
422+
time.Sleep(50 * time.Millisecond)
423+
424+
if receivedSchema == nil {
425+
t.Fatal("Expected handler to receive schema, got nil")
426+
}
427+
if receivedSchema["type"] != "object" {
428+
t.Errorf("Expected schema type 'object', got %v", receivedSchema["type"])
429+
}
430+
props, ok := receivedSchema["properties"].(map[string]any)
431+
if !ok || props == nil {
432+
t.Fatal("Expected schema properties map")
433+
}
434+
if len(props) != 2 {
435+
t.Errorf("Expected 2 properties, got %d", len(props))
436+
}
437+
req, ok := receivedSchema["required"].([]string)
438+
if !ok || len(req) != 2 {
439+
t.Errorf("Expected required [name, age], got %v", receivedSchema["required"])
440+
}
441+
})
396442
}

python/test_commands_and_elicitation.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,76 @@ async def mock_request(method, params):
546546
finally:
547547
await client.force_stop()
548548

549+
@pytest.mark.asyncio
550+
async def test_elicitation_handler_receives_full_schema(self):
551+
"""Verifies that requestedSchema passes type, properties, and required to handler."""
552+
client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
553+
await client.start()
554+
555+
try:
556+
handler_calls: list = []
557+
558+
async def elicitation_handler(
559+
request: ElicitationRequest, invocation: dict[str, str]
560+
) -> ElicitationResult:
561+
handler_calls.append(request)
562+
return {"action": "cancel"}
563+
564+
session = await client.create_session(
565+
on_permission_request=PermissionHandler.approve_all,
566+
on_elicitation_request=elicitation_handler,
567+
)
568+
569+
original_request = client._client.request
570+
571+
async def mock_request(method, params):
572+
if method == "session.ui.handlePendingElicitation":
573+
return {"success": True}
574+
return await original_request(method, params)
575+
576+
client._client.request = mock_request
577+
578+
from copilot.generated.session_events import (
579+
Data,
580+
RequestedSchema,
581+
RequestedSchemaType,
582+
SessionEvent,
583+
SessionEventType,
584+
)
585+
586+
event = SessionEvent(
587+
data=Data(
588+
request_id="req-schema-1",
589+
message="Fill in your details",
590+
requested_schema=RequestedSchema(
591+
type=RequestedSchemaType.OBJECT,
592+
properties={
593+
"name": {"type": "string"},
594+
"age": {"type": "number"},
595+
},
596+
required=["name", "age"],
597+
),
598+
),
599+
id="evt-schema-1",
600+
timestamp="2025-01-01T00:00:00Z",
601+
type=SessionEventType.ELICITATION_REQUESTED,
602+
ephemeral=True,
603+
parent_id=None,
604+
)
605+
session._dispatch_event(event)
606+
607+
await asyncio.sleep(0.2)
608+
609+
assert len(handler_calls) == 1
610+
schema = handler_calls[0].get("requestedSchema")
611+
assert schema is not None, "Expected requestedSchema in handler call"
612+
assert schema["type"] == "object"
613+
assert "name" in schema["properties"]
614+
assert "age" in schema["properties"]
615+
assert schema["required"] == ["name", "age"]
616+
finally:
617+
await client.force_stop()
618+
549619

550620
# ============================================================================
551621
# Capabilities changed event

0 commit comments

Comments
 (0)