From 606df798b4d05cda5379d692843ac8daad2beda7 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 09:14:59 -0700 Subject: [PATCH 01/11] refactor(go): update handwritten files to use prefixed enum constant names Rename all references to copilot session event type and rpc constants to use the new prefixed naming convention matching the generated code: - copilot.SessionCompactionStart -> copilot.SessionEventTypeSessionCompactionStart - copilot.ExternalToolRequested -> copilot.SessionEventTypeExternalToolRequested - copilot.PermissionRequested -> copilot.SessionEventTypePermissionRequested - copilot.ToolExecutionStart -> copilot.SessionEventTypeToolExecutionStart - copilot.AssistantReasoning -> copilot.SessionEventTypeAssistantReasoning - copilot.Abort -> copilot.SessionEventTypeAbort - rpc.Interactive -> rpc.ModeInteractive - rpc.Plan -> rpc.ModePlan - rpc.Warning -> rpc.LevelWarning - rpc.Error -> rpc.LevelError - rpc.Info -> rpc.LevelInfo (and all other constants listed in the rename) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/internal/e2e/compaction_test.go | 6 +++--- go/internal/e2e/multi_client_test.go | 24 +++++++++++------------ go/internal/e2e/permissions_test.go | 4 ++-- go/internal/e2e/rpc_test.go | 12 ++++++------ go/internal/e2e/session_test.go | 20 +++++++++---------- go/internal/e2e/testharness/helper.go | 2 +- go/samples/chat.go | 4 ++-- go/session.go | 28 +++++++++++++-------------- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/go/internal/e2e/compaction_test.go b/go/internal/e2e/compaction_test.go index aee80704d..888ab2aa9 100644 --- a/go/internal/e2e/compaction_test.go +++ b/go/internal/e2e/compaction_test.go @@ -36,10 +36,10 @@ func TestCompaction(t *testing.T) { var compactionCompleteEvents []copilot.SessionEvent session.On(func(event copilot.SessionEvent) { - if event.Type == copilot.SessionCompactionStart { + if event.Type == copilot.SessionEventTypeSessionCompactionStart { compactionStartEvents = append(compactionStartEvents, event) } - if event.Type == copilot.SessionCompactionComplete { + if event.Type == copilot.SessionEventTypeSessionCompactionComplete { compactionCompleteEvents = append(compactionCompleteEvents, event) } }) @@ -105,7 +105,7 @@ func TestCompaction(t *testing.T) { var compactionEvents []copilot.SessionEvent session.On(func(event copilot.SessionEvent) { - if event.Type == copilot.SessionCompactionStart || event.Type == copilot.SessionCompactionComplete { + if event.Type == copilot.SessionEventTypeSessionCompactionStart || event.Type == copilot.SessionEventTypeSessionCompactionComplete { compactionEvents = append(compactionEvents, event) } }) diff --git a/go/internal/e2e/multi_client_test.go b/go/internal/e2e/multi_client_test.go index 9571ab58e..bc3f757f0 100644 --- a/go/internal/e2e/multi_client_test.go +++ b/go/internal/e2e/multi_client_test.go @@ -79,13 +79,13 @@ func TestMultiClient(t *testing.T) { client2Completed := make(chan struct{}, 1) session1.On(func(event copilot.SessionEvent) { - if event.Type == copilot.ExternalToolRequested { + if event.Type == copilot.SessionEventTypeExternalToolRequested { select { case client1Requested <- struct{}{}: default: } } - if event.Type == copilot.ExternalToolCompleted { + if event.Type == copilot.SessionEventTypeExternalToolCompleted { select { case client1Completed <- struct{}{}: default: @@ -93,13 +93,13 @@ func TestMultiClient(t *testing.T) { } }) session2.On(func(event copilot.SessionEvent) { - if event.Type == copilot.ExternalToolRequested { + if event.Type == copilot.SessionEventTypeExternalToolRequested { select { case client2Requested <- struct{}{}: default: } } - if event.Type == copilot.ExternalToolCompleted { + if event.Type == copilot.SessionEventTypeExternalToolCompleted { select { case client2Completed <- struct{}{}: default: @@ -197,10 +197,10 @@ func TestMultiClient(t *testing.T) { // Both clients should have seen permission.requested events mu1.Lock() - c1PermRequested := filterEventsByType(client1Events, copilot.PermissionRequested) + c1PermRequested := filterEventsByType(client1Events, copilot.SessionEventTypePermissionRequested) mu1.Unlock() mu2.Lock() - c2PermRequested := filterEventsByType(client2Events, copilot.PermissionRequested) + c2PermRequested := filterEventsByType(client2Events, copilot.SessionEventTypePermissionRequested) mu2.Unlock() if len(c1PermRequested) == 0 { @@ -212,10 +212,10 @@ func TestMultiClient(t *testing.T) { // Both clients should have seen permission.completed events with approved result mu1.Lock() - c1PermCompleted := filterEventsByType(client1Events, copilot.PermissionCompleted) + c1PermCompleted := filterEventsByType(client1Events, copilot.SessionEventTypePermissionCompleted) mu1.Unlock() mu2.Lock() - c2PermCompleted := filterEventsByType(client2Events, copilot.PermissionCompleted) + c2PermCompleted := filterEventsByType(client2Events, copilot.SessionEventTypePermissionCompleted) mu2.Unlock() if len(c1PermCompleted) == 0 { @@ -293,10 +293,10 @@ func TestMultiClient(t *testing.T) { // Both clients should have seen permission.requested events mu1.Lock() - c1PermRequested := filterEventsByType(client1Events, copilot.PermissionRequested) + c1PermRequested := filterEventsByType(client1Events, copilot.SessionEventTypePermissionRequested) mu1.Unlock() mu2.Lock() - c2PermRequested := filterEventsByType(client2Events, copilot.PermissionRequested) + c2PermRequested := filterEventsByType(client2Events, copilot.SessionEventTypePermissionRequested) mu2.Unlock() if len(c1PermRequested) == 0 { @@ -308,10 +308,10 @@ func TestMultiClient(t *testing.T) { // Both clients should see the denial in the completed event mu1.Lock() - c1PermCompleted := filterEventsByType(client1Events, copilot.PermissionCompleted) + c1PermCompleted := filterEventsByType(client1Events, copilot.SessionEventTypePermissionCompleted) mu1.Unlock() mu2.Lock() - c2PermCompleted := filterEventsByType(client2Events, copilot.PermissionCompleted) + c2PermCompleted := filterEventsByType(client2Events, copilot.SessionEventTypePermissionCompleted) mu2.Unlock() if len(c1PermCompleted) == 0 { diff --git a/go/internal/e2e/permissions_test.go b/go/internal/e2e/permissions_test.go index 328e7e788..98f620043 100644 --- a/go/internal/e2e/permissions_test.go +++ b/go/internal/e2e/permissions_test.go @@ -173,7 +173,7 @@ func TestPermissions(t *testing.T) { permissionDenied := false session.On(func(event copilot.SessionEvent) { - if event.Type == copilot.ToolExecutionComplete && + if event.Type == copilot.SessionEventTypeToolExecutionComplete && event.Data.Success != nil && !*event.Data.Success && event.Data.Error != nil && event.Data.Error.ErrorClass != nil && strings.Contains(event.Data.Error.ErrorClass.Message, "Permission denied") { @@ -223,7 +223,7 @@ func TestPermissions(t *testing.T) { permissionDenied := false session2.On(func(event copilot.SessionEvent) { - if event.Type == copilot.ToolExecutionComplete && + if event.Type == copilot.SessionEventTypeToolExecutionComplete && event.Data.Success != nil && !*event.Data.Success && event.Data.Error != nil && event.Data.Error.ErrorClass != nil && strings.Contains(event.Data.Error.ErrorClass.Message, "Permission denied") { diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index ebcbe1130..3d69b97ad 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -219,16 +219,16 @@ func TestSessionRpc(t *testing.T) { if err != nil { t.Fatalf("Failed to get mode: %v", err) } - if initial.Mode != rpc.Interactive { + if initial.Mode != rpc.ModeInteractive { t.Errorf("Expected initial mode 'interactive', got %q", initial.Mode) } // Switch to plan mode - planResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.Plan}) + planResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.ModePlan}) if err != nil { t.Fatalf("Failed to set mode to plan: %v", err) } - if planResult.Mode != rpc.Plan { + if planResult.Mode != rpc.ModePlan { t.Errorf("Expected mode 'plan', got %q", planResult.Mode) } @@ -237,16 +237,16 @@ func TestSessionRpc(t *testing.T) { if err != nil { t.Fatalf("Failed to get mode after plan: %v", err) } - if afterPlan.Mode != rpc.Plan { + if afterPlan.Mode != rpc.ModePlan { t.Errorf("Expected mode 'plan' after set, got %q", afterPlan.Mode) } // Switch back to interactive - interactiveResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.Interactive}) + interactiveResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.ModeInteractive}) if err != nil { t.Fatalf("Failed to set mode to interactive: %v", err) } - if interactiveResult.Mode != rpc.Interactive { + if interactiveResult.Mode != rpc.ModeInteractive { t.Errorf("Expected mode 'interactive', got %q", interactiveResult.Mode) } }) diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index c3c9cc009..f6542e9f7 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -506,7 +506,7 @@ func TestSession(t *testing.T) { toolStartCh := make(chan *copilot.SessionEvent, 1) toolStartErrCh := make(chan error, 1) go func() { - evt, err := testharness.GetNextEventOfType(session, copilot.ToolExecutionStart, 60*time.Second) + evt, err := testharness.GetNextEventOfType(session, copilot.SessionEventTypeToolExecutionStart, 60*time.Second) if err != nil { toolStartErrCh <- err } else { @@ -517,7 +517,7 @@ func TestSession(t *testing.T) { sessionIdleCh := make(chan *copilot.SessionEvent, 1) sessionIdleErrCh := make(chan error, 1) go func() { - evt, err := testharness.GetNextEventOfType(session, copilot.SessionIdle, 60*time.Second) + evt, err := testharness.GetNextEventOfType(session, copilot.SessionEventTypeSessionIdle, 60*time.Second) if err != nil { sessionIdleErrCh <- err } else { @@ -565,7 +565,7 @@ func TestSession(t *testing.T) { // Verify messages contain an abort event hasAbortEvent := false for _, msg := range messages { - if msg.Type == copilot.Abort { + if msg.Type == copilot.SessionEventTypeAbort { hasAbortEvent = true break } @@ -913,7 +913,7 @@ func TestSetModelWithReasoningEffort(t *testing.T) { modelChanged := make(chan copilot.SessionEvent, 1) session.On(func(event copilot.SessionEvent) { - if event.Type == copilot.SessionModelChange { + if event.Type == copilot.SessionEventTypeSessionModelChange { select { case modelChanged <- event: default: @@ -986,7 +986,7 @@ func TestSessionLog(t *testing.T) { t.Fatalf("Log failed: %v", err) } - evt := waitForEvent(t, &mu, &events, copilot.SessionInfo, "Info message", 5*time.Second) + evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionInfo, "Info message", 5*time.Second) if evt.Data.InfoType == nil || *evt.Data.InfoType != "notification" { t.Errorf("Expected infoType 'notification', got %v", evt.Data.InfoType) } @@ -996,11 +996,11 @@ func TestSessionLog(t *testing.T) { }) t.Run("should log warning message", func(t *testing.T) { - if err := session.Log(t.Context(), "Warning message", &copilot.LogOptions{Level: rpc.Warning}); err != nil { + if err := session.Log(t.Context(), "Warning message", &copilot.LogOptions{Level: rpc.LevelWarning}); err != nil { t.Fatalf("Log failed: %v", err) } - evt := waitForEvent(t, &mu, &events, copilot.SessionWarning, "Warning message", 5*time.Second) + evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionWarning, "Warning message", 5*time.Second) if evt.Data.WarningType == nil || *evt.Data.WarningType != "notification" { t.Errorf("Expected warningType 'notification', got %v", evt.Data.WarningType) } @@ -1010,11 +1010,11 @@ func TestSessionLog(t *testing.T) { }) t.Run("should log error message", func(t *testing.T) { - if err := session.Log(t.Context(), "Error message", &copilot.LogOptions{Level: rpc.Error}); err != nil { + if err := session.Log(t.Context(), "Error message", &copilot.LogOptions{Level: rpc.LevelError}); err != nil { t.Fatalf("Log failed: %v", err) } - evt := waitForEvent(t, &mu, &events, copilot.SessionError, "Error message", 5*time.Second) + evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionError, "Error message", 5*time.Second) if evt.Data.ErrorType == nil || *evt.Data.ErrorType != "notification" { t.Errorf("Expected errorType 'notification', got %v", evt.Data.ErrorType) } @@ -1028,7 +1028,7 @@ func TestSessionLog(t *testing.T) { t.Fatalf("Log failed: %v", err) } - evt := waitForEvent(t, &mu, &events, copilot.SessionInfo, "Ephemeral message", 5*time.Second) + evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionInfo, "Ephemeral message", 5*time.Second) if evt.Data.InfoType == nil || *evt.Data.InfoType != "notification" { t.Errorf("Expected infoType 'notification', got %v", evt.Data.InfoType) } diff --git a/go/internal/e2e/testharness/helper.go b/go/internal/e2e/testharness/helper.go index 05947c806..3b521f330 100644 --- a/go/internal/e2e/testharness/helper.go +++ b/go/internal/e2e/testharness/helper.go @@ -67,7 +67,7 @@ func GetNextEventOfType(session *copilot.Session, eventType copilot.SessionEvent case result <- &event: default: } - case copilot.SessionError: + case copilot.SessionEventTypeSessionError: msg := "session error" if event.Data.Message != nil { msg = *event.Data.Message diff --git a/go/samples/chat.go b/go/samples/chat.go index f984f758a..4d5e98d7d 100644 --- a/go/samples/chat.go +++ b/go/samples/chat.go @@ -35,11 +35,11 @@ func main() { session.On(func(event copilot.SessionEvent) { var output string switch event.Type { - case copilot.AssistantReasoning: + case copilot.SessionEventTypeAssistantReasoning: if event.Data.Content != nil { output = fmt.Sprintf("[reasoning: %s]", *event.Data.Content) } - case copilot.ToolExecutionStart: + case copilot.SessionEventTypeToolExecutionStart: if event.Data.ToolName != nil { output = fmt.Sprintf("[tool: %s]", *event.Data.ToolName) } diff --git a/go/session.go b/go/session.go index d2a5785be..587aacfa9 100644 --- a/go/session.go +++ b/go/session.go @@ -182,17 +182,17 @@ func (s *Session) SendAndWait(ctx context.Context, options MessageOptions) (*Ses unsubscribe := s.On(func(event SessionEvent) { switch event.Type { - case AssistantMessage: + case SessionEventTypeAssistantMessage: mu.Lock() eventCopy := event lastAssistantMessage = &eventCopy mu.Unlock() - case SessionIdle: + case SessionEventTypeSessionIdle: select { case idleCh <- struct{}{}: default: } - case SessionError: + case SessionEventTypeSessionError: errMsg := "session error" if event.Data.Message != nil { errMsg = *event.Data.Message @@ -501,7 +501,7 @@ func (s *Session) processEvents() { // cause RPC deadlocks. func (s *Session) handleBroadcastEvent(event SessionEvent) { switch event.Type { - case ExternalToolRequested: + case SessionEventTypeExternalToolRequested: requestID := event.Data.RequestID toolName := event.Data.ToolName if requestID == nil || toolName == nil { @@ -524,7 +524,7 @@ func (s *Session) handleBroadcastEvent(event SessionEvent) { } s.executeToolAndRespond(*requestID, *toolName, toolCallID, event.Data.Arguments, handler, tp, ts) - case PermissionRequested: + case SessionEventTypePermissionRequested: requestID := event.Data.RequestID if requestID == nil || event.Data.PermissionRequest == nil { return @@ -544,8 +544,8 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, if r := recover(); r != nil { errMsg := fmt.Sprintf("tool panic: %v", r) s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.SessionToolsHandlePendingToolCallParams{ - RequestID: requestID, - Error: &errMsg, + RequestID: requestID, + LevelError: &errMsg, }) } }() @@ -562,8 +562,8 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, if err != nil { errMsg := err.Error() s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.SessionToolsHandlePendingToolCallParams{ - RequestID: requestID, - Error: &errMsg, + RequestID: requestID, + LevelError: &errMsg, }) return } @@ -585,7 +585,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.SessionPermissionsHandlePendingPermissionRequestParams{ RequestID: requestID, Result: rpc.SessionPermissionsHandlePendingPermissionRequestParamsResult{ - Kind: rpc.DeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) } @@ -600,7 +600,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.SessionPermissionsHandlePendingPermissionRequestParams{ RequestID: requestID, Result: rpc.SessionPermissionsHandlePendingPermissionRequestParamsResult{ - Kind: rpc.DeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) return @@ -770,8 +770,8 @@ func (s *Session) SetModel(ctx context.Context, model string, opts ...SetModelOp // LogOptions configures optional parameters for [Session.Log]. type LogOptions struct { - // Level sets the log severity. Valid values are [rpc.Info] (default), - // [rpc.Warning], and [rpc.Error]. + // Level sets the log severity. Valid values are [rpc.LevelInfo] (default), + // [rpc.LevelWarning], and [rpc.LevelError]. Level rpc.Level // Ephemeral marks the message as transient so it is not persisted // to the session event log on disk. When nil the server decides the @@ -791,7 +791,7 @@ type LogOptions struct { // session.Log(ctx, "Processing started") // // // Warning with options -// session.Log(ctx, "Rate limit approaching", &copilot.LogOptions{Level: rpc.Warning}) +// session.Log(ctx, "Rate limit approaching", &copilot.LogOptions{Level: rpc.LevelWarning}) // // // Ephemeral message (not persisted) // session.Log(ctx, "Working...", &copilot.LogOptions{Ephemeral: copilot.Bool(true)}) From 97bc33da82eb2ae5536c618141dc76eb90d24036 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 10:13:06 -0700 Subject: [PATCH 02/11] fix: Go codegen enum prefixes and type name reconciliation - Add 'mcp' to goInitialisms so toPascalCase produces SessionMCP* matching quicktype - Post-process enum constants to use canonical Go TypeNameValue convention (replaces quicktype's Purple/Fluffy/Tentacled prefixes and unprefixed constants) - Reconcile type names: extract actual quicktype-generated struct names and use them in RPC wrapper code instead of recomputing via toPascalCase - Extract field name mappings from quicktype output to handle keyword-avoidance renames - Update all handwritten Go references to use new prefixed constant names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/generated_session_events.go | 260 ++++++++++++++++----------------- go/rpc/generated_rpc.go | 72 ++++----- go/session.go | 8 +- scripts/codegen/go.ts | 114 +++++++++++++-- 4 files changed, 270 insertions(+), 184 deletions(-) diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 9c28c07f3..ecc1a775e 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -1243,78 +1243,78 @@ type ToolRequest struct { type AgentMode string const ( - AgentModeShell AgentMode = "shell" - Autopilot AgentMode = "autopilot" - Interactive AgentMode = "interactive" - Plan AgentMode = "plan" + AgentModeShell AgentMode = "shell" + AgentModeAutopilot AgentMode = "autopilot" + AgentModeInteractive AgentMode = "interactive" + AgentModePlan AgentMode = "plan" ) // Type of GitHub reference type ReferenceType string const ( - Discussion ReferenceType = "discussion" - Issue ReferenceType = "issue" - PR ReferenceType = "pr" + ReferenceTypeDiscussion ReferenceType = "discussion" + ReferenceTypeIssue ReferenceType = "issue" + ReferenceTypePr ReferenceType = "pr" ) type AttachmentType string const ( - Blob AttachmentType = "blob" - Directory AttachmentType = "directory" - File AttachmentType = "file" - GithubReference AttachmentType = "github_reference" - Selection AttachmentType = "selection" + AttachmentTypeBlob AttachmentType = "blob" + AttachmentTypeDirectory AttachmentType = "directory" + AttachmentTypeFile AttachmentType = "file" + AttachmentTypeGithubReference AttachmentType = "github_reference" + AttachmentTypeSelection AttachmentType = "selection" ) // Hosting platform type of the repository (github or ado) type HostType string const ( - ADO HostType = "ado" - Github HostType = "github" + HostTypeAdo HostType = "ado" + HostTypeGithub HostType = "github" ) // Discovery source type Source string const ( - Project Source = "project" - User Source = "user" + SourceProject Source = "project" + SourceUser Source = "user" ) // Current status: running, disabled, failed, or starting type ExtensionStatus string const ( - PurpleDisabled ExtensionStatus = "disabled" - PurpleFailed ExtensionStatus = "failed" - Running ExtensionStatus = "running" - Starting ExtensionStatus = "starting" + ExtensionStatusDisabled ExtensionStatus = "disabled" + ExtensionStatusFailed ExtensionStatus = "failed" + ExtensionStatusRunning ExtensionStatus = "running" + ExtensionStatusStarting ExtensionStatus = "starting" ) // Whether the agent completed successfully or failed type KindStatus string const ( - Completed KindStatus = "completed" - FluffyFailed KindStatus = "failed" + KindStatusCompleted KindStatus = "completed" + KindStatusFailed KindStatus = "failed" ) type KindType string const ( - AgentCompleted KindType = "agent_completed" - AgentIdle KindType = "agent_idle" - ShellCompleted KindType = "shell_completed" - ShellDetachedCompleted KindType = "shell_detached_completed" + KindTypeAgentCompleted KindType = "agent_completed" + KindTypeAgentIdle KindType = "agent_idle" + KindTypeShellCompleted KindType = "shell_completed" + KindTypeShellDetachedCompleted KindType = "shell_detached_completed" ) type Mode string const ( - Form Mode = "form" + ModeForm Mode = "form" ) // The type of operation performed on the plan file @@ -1323,66 +1323,66 @@ const ( type Operation string const ( - Create Operation = "create" - Delete Operation = "delete" - Update Operation = "update" + OperationCreate Operation = "create" + OperationDelete Operation = "delete" + OperationUpdate Operation = "update" ) type PermissionRequestKind string const ( - CustomTool PermissionRequestKind = "custom-tool" - Hook PermissionRequestKind = "hook" - KindShell PermissionRequestKind = "shell" - MCP PermissionRequestKind = "mcp" - Memory PermissionRequestKind = "memory" - Read PermissionRequestKind = "read" - URL PermissionRequestKind = "url" - Write PermissionRequestKind = "write" + PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" + PermissionRequestKindHook PermissionRequestKind = "hook" + PermissionRequestKindShell PermissionRequestKind = "shell" + PermissionRequestKindMcp PermissionRequestKind = "mcp" + PermissionRequestKindMemory PermissionRequestKind = "memory" + PermissionRequestKindRead PermissionRequestKind = "read" + PermissionRequestKindUrl PermissionRequestKind = "url" + PermissionRequestKindWrite PermissionRequestKind = "write" ) type RequestedSchemaType string const ( - Object RequestedSchemaType = "object" + RequestedSchemaTypeObject RequestedSchemaType = "object" ) // Theme variant this icon is intended for type Theme string const ( - Dark Theme = "dark" - Light Theme = "light" + ThemeDark Theme = "dark" + ThemeLight Theme = "light" ) type ContentType string const ( - Audio ContentType = "audio" - Image ContentType = "image" - Resource ContentType = "resource" - ResourceLink ContentType = "resource_link" - Terminal ContentType = "terminal" - Text ContentType = "text" + ContentTypeAudio ContentType = "audio" + ContentTypeImage ContentType = "image" + ContentTypeResource ContentType = "resource" + ContentTypeResourceLink ContentType = "resource_link" + ContentTypeTerminal ContentType = "terminal" + ContentTypeText ContentType = "text" ) // The outcome of the permission request type ResultKind string const ( - Approved ResultKind = "approved" - DeniedByContentExclusionPolicy ResultKind = "denied-by-content-exclusion-policy" - DeniedByRules ResultKind = "denied-by-rules" - DeniedInteractivelyByUser ResultKind = "denied-interactively-by-user" - DeniedNoApprovalRuleAndCouldNotRequestFromUser ResultKind = "denied-no-approval-rule-and-could-not-request-from-user" + ResultKindApproved ResultKind = "approved" + ResultKindDeniedByContentExclusionPolicy ResultKind = "denied-by-content-exclusion-policy" + ResultKindDeniedByRules ResultKind = "denied-by-rules" + ResultKindDeniedInteractivelyByUser ResultKind = "denied-interactively-by-user" + ResultKindDeniedNoApprovalRuleAndCouldNotRequestFromUser ResultKind = "denied-no-approval-rule-and-could-not-request-from-user" ) // Message role: "system" for system prompts, "developer" for developer-injected instructions type Role string const ( - Developer Role = "developer" - System Role = "system" + RoleDeveloper Role = "developer" + RoleSystem Role = "system" ) // Connection status: connected, failed, pending, disabled, or not_configured @@ -1391,27 +1391,27 @@ const ( type ServerStatus string const ( - Connected ServerStatus = "connected" - FluffyDisabled ServerStatus = "disabled" - NotConfigured ServerStatus = "not_configured" - Pending ServerStatus = "pending" - TentacledFailed ServerStatus = "failed" + ServerStatusConnected ServerStatus = "connected" + ServerStatusDisabled ServerStatus = "disabled" + ServerStatusNotConfigured ServerStatus = "not_configured" + ServerStatusPending ServerStatus = "pending" + ServerStatusFailed ServerStatus = "failed" ) // Whether the session ended normally ("routine") or due to a crash/fatal error ("error") type ShutdownType string const ( - Error ShutdownType = "error" - Routine ShutdownType = "routine" + ShutdownTypeError ShutdownType = "error" + ShutdownTypeRoutine ShutdownType = "routine" ) // Origin type of the session being handed off type SourceType string const ( - Local SourceType = "local" - Remote SourceType = "remote" + SourceTypeLocal SourceType = "local" + SourceTypeRemote SourceType = "remote" ) // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool @@ -1419,78 +1419,78 @@ const ( type ToolRequestType string const ( - Custom ToolRequestType = "custom" - Function ToolRequestType = "function" + ToolRequestTypeCustom ToolRequestType = "custom" + ToolRequestTypeFunction ToolRequestType = "function" ) type SessionEventType string const ( - Abort SessionEventType = "abort" - AssistantIntent SessionEventType = "assistant.intent" - AssistantMessage SessionEventType = "assistant.message" - AssistantMessageDelta SessionEventType = "assistant.message_delta" - AssistantReasoning SessionEventType = "assistant.reasoning" - AssistantReasoningDelta SessionEventType = "assistant.reasoning_delta" - AssistantStreamingDelta SessionEventType = "assistant.streaming_delta" - AssistantTurnEnd SessionEventType = "assistant.turn_end" - AssistantTurnStart SessionEventType = "assistant.turn_start" - AssistantUsage SessionEventType = "assistant.usage" - CommandCompleted SessionEventType = "command.completed" - CommandQueued SessionEventType = "command.queued" - ElicitationCompleted SessionEventType = "elicitation.completed" - ElicitationRequested SessionEventType = "elicitation.requested" - ExitPlanModeCompleted SessionEventType = "exit_plan_mode.completed" - ExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" - ExternalToolCompleted SessionEventType = "external_tool.completed" - ExternalToolRequested SessionEventType = "external_tool.requested" - HookEnd SessionEventType = "hook.end" - HookStart SessionEventType = "hook.start" - PendingMessagesModified SessionEventType = "pending_messages.modified" - PermissionCompleted SessionEventType = "permission.completed" - PermissionRequested SessionEventType = "permission.requested" - SessionBackgroundTasksChanged SessionEventType = "session.background_tasks_changed" - SessionCompactionComplete SessionEventType = "session.compaction_complete" - SessionCompactionStart SessionEventType = "session.compaction_start" - SessionContextChanged SessionEventType = "session.context_changed" - SessionError SessionEventType = "session.error" - SessionExtensionsLoaded SessionEventType = "session.extensions_loaded" - SessionHandoff SessionEventType = "session.handoff" - SessionIdle SessionEventType = "session.idle" - SessionInfo SessionEventType = "session.info" - SessionMCPServerStatusChanged SessionEventType = "session.mcp_server_status_changed" - SessionMCPServersLoaded SessionEventType = "session.mcp_servers_loaded" - SessionModeChanged SessionEventType = "session.mode_changed" - SessionModelChange SessionEventType = "session.model_change" - SessionPlanChanged SessionEventType = "session.plan_changed" - SessionResume SessionEventType = "session.resume" - SessionShutdown SessionEventType = "session.shutdown" - SessionSkillsLoaded SessionEventType = "session.skills_loaded" - SessionSnapshotRewind SessionEventType = "session.snapshot_rewind" - SessionStart SessionEventType = "session.start" - SessionTaskComplete SessionEventType = "session.task_complete" - SessionTitleChanged SessionEventType = "session.title_changed" - SessionToolsUpdated SessionEventType = "session.tools_updated" - SessionTruncation SessionEventType = "session.truncation" - SessionUsageInfo SessionEventType = "session.usage_info" - SessionWarning SessionEventType = "session.warning" - SessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" - SkillInvoked SessionEventType = "skill.invoked" - SubagentCompleted SessionEventType = "subagent.completed" - SubagentDeselected SessionEventType = "subagent.deselected" - SubagentFailed SessionEventType = "subagent.failed" - SubagentSelected SessionEventType = "subagent.selected" - SubagentStarted SessionEventType = "subagent.started" - SystemMessage SessionEventType = "system.message" - SystemNotification SessionEventType = "system.notification" - ToolExecutionComplete SessionEventType = "tool.execution_complete" - ToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" - ToolExecutionProgress SessionEventType = "tool.execution_progress" - ToolExecutionStart SessionEventType = "tool.execution_start" - ToolUserRequested SessionEventType = "tool.user_requested" - UserInputCompleted SessionEventType = "user_input.completed" - UserInputRequested SessionEventType = "user_input.requested" - UserMessage SessionEventType = "user.message" + SessionEventTypeAbort SessionEventType = "abort" + SessionEventTypeAssistantIntent SessionEventType = "assistant.intent" + SessionEventTypeAssistantMessage SessionEventType = "assistant.message" + SessionEventTypeAssistantMessageDelta SessionEventType = "assistant.message_delta" + SessionEventTypeAssistantReasoning SessionEventType = "assistant.reasoning" + SessionEventTypeAssistantReasoningDelta SessionEventType = "assistant.reasoning_delta" + SessionEventTypeAssistantStreamingDelta SessionEventType = "assistant.streaming_delta" + SessionEventTypeAssistantTurnEnd SessionEventType = "assistant.turn_end" + SessionEventTypeAssistantTurnStart SessionEventType = "assistant.turn_start" + SessionEventTypeAssistantUsage SessionEventType = "assistant.usage" + SessionEventTypeCommandCompleted SessionEventType = "command.completed" + SessionEventTypeCommandQueued SessionEventType = "command.queued" + SessionEventTypeElicitationCompleted SessionEventType = "elicitation.completed" + SessionEventTypeElicitationRequested SessionEventType = "elicitation.requested" + SessionEventTypeExitPlanModeCompleted SessionEventType = "exit_plan_mode.completed" + SessionEventTypeExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" + SessionEventTypeExternalToolCompleted SessionEventType = "external_tool.completed" + SessionEventTypeExternalToolRequested SessionEventType = "external_tool.requested" + SessionEventTypeHookEnd SessionEventType = "hook.end" + SessionEventTypeHookStart SessionEventType = "hook.start" + SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" + SessionEventTypePermissionCompleted SessionEventType = "permission.completed" + SessionEventTypePermissionRequested SessionEventType = "permission.requested" + SessionEventTypeSessionBackgroundTasksChanged SessionEventType = "session.background_tasks_changed" + SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete" + SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start" + SessionEventTypeSessionContextChanged SessionEventType = "session.context_changed" + SessionEventTypeSessionError SessionEventType = "session.error" + SessionEventTypeSessionExtensionsLoaded SessionEventType = "session.extensions_loaded" + SessionEventTypeSessionHandoff SessionEventType = "session.handoff" + SessionEventTypeSessionIdle SessionEventType = "session.idle" + SessionEventTypeSessionInfo SessionEventType = "session.info" + SessionEventTypeSessionMcpServerStatusChanged SessionEventType = "session.mcp_server_status_changed" + SessionEventTypeSessionMcpServersLoaded SessionEventType = "session.mcp_servers_loaded" + SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed" + SessionEventTypeSessionModelChange SessionEventType = "session.model_change" + SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed" + SessionEventTypeSessionResume SessionEventType = "session.resume" + SessionEventTypeSessionShutdown SessionEventType = "session.shutdown" + SessionEventTypeSessionSkillsLoaded SessionEventType = "session.skills_loaded" + SessionEventTypeSessionSnapshotRewind SessionEventType = "session.snapshot_rewind" + SessionEventTypeSessionStart SessionEventType = "session.start" + SessionEventTypeSessionTaskComplete SessionEventType = "session.task_complete" + SessionEventTypeSessionTitleChanged SessionEventType = "session.title_changed" + SessionEventTypeSessionToolsUpdated SessionEventType = "session.tools_updated" + SessionEventTypeSessionTruncation SessionEventType = "session.truncation" + SessionEventTypeSessionUsageInfo SessionEventType = "session.usage_info" + SessionEventTypeSessionWarning SessionEventType = "session.warning" + SessionEventTypeSessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" + SessionEventTypeSkillInvoked SessionEventType = "skill.invoked" + SessionEventTypeSubagentCompleted SessionEventType = "subagent.completed" + SessionEventTypeSubagentDeselected SessionEventType = "subagent.deselected" + SessionEventTypeSubagentFailed SessionEventType = "subagent.failed" + SessionEventTypeSubagentSelected SessionEventType = "subagent.selected" + SessionEventTypeSubagentStarted SessionEventType = "subagent.started" + SessionEventTypeSystemMessage SessionEventType = "system.message" + SessionEventTypeSystemNotification SessionEventType = "system.notification" + SessionEventTypeToolExecutionComplete SessionEventType = "tool.execution_complete" + SessionEventTypeToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" + SessionEventTypeToolExecutionProgress SessionEventType = "tool.execution_progress" + SessionEventTypeToolExecutionStart SessionEventType = "tool.execution_start" + SessionEventTypeToolUserRequested SessionEventType = "tool.user_requested" + SessionEventTypeUserInputCompleted SessionEventType = "user_input.completed" + SessionEventTypeUserInputRequested SessionEventType = "user_input.requested" + SessionEventTypeUserMessage SessionEventType = "user.message" ) type ContextUnion struct { diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 7929994cf..6579ab695 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -506,48 +506,48 @@ type SessionShellKillParams struct { type Mode string const ( - Autopilot Mode = "autopilot" - Interactive Mode = "interactive" - Plan Mode = "plan" + ModeAutopilot Mode = "autopilot" + ModeInteractive Mode = "interactive" + ModePlan Mode = "plan" ) // Connection status: connected, failed, pending, disabled, or not_configured type ServerStatus string const ( - Connected ServerStatus = "connected" - NotConfigured ServerStatus = "not_configured" - Pending ServerStatus = "pending" - PurpleDisabled ServerStatus = "disabled" - PurpleFailed ServerStatus = "failed" + ServerStatusConnected ServerStatus = "connected" + ServerStatusNotConfigured ServerStatus = "not_configured" + ServerStatusPending ServerStatus = "pending" + ServerStatusDisabled ServerStatus = "disabled" + ServerStatusFailed ServerStatus = "failed" ) // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) type Source string const ( - Project Source = "project" - User Source = "user" + SourceProject Source = "project" + SourceUser Source = "user" ) // Current status: running, disabled, failed, or starting type ExtensionStatus string const ( - FluffyDisabled ExtensionStatus = "disabled" - FluffyFailed ExtensionStatus = "failed" - Running ExtensionStatus = "running" - Starting ExtensionStatus = "starting" + ExtensionStatusDisabled ExtensionStatus = "disabled" + ExtensionStatusFailed ExtensionStatus = "failed" + ExtensionStatusRunning ExtensionStatus = "running" + ExtensionStatusStarting ExtensionStatus = "starting" ) type Kind string const ( - Approved Kind = "approved" - DeniedByContentExclusionPolicy Kind = "denied-by-content-exclusion-policy" - DeniedByRules Kind = "denied-by-rules" - DeniedInteractivelyByUser Kind = "denied-interactively-by-user" - DeniedNoApprovalRuleAndCouldNotRequestFromUser Kind = "denied-no-approval-rule-and-could-not-request-from-user" + KindApproved Kind = "approved" + KindDeniedByContentExclusionPolicy Kind = "denied-by-content-exclusion-policy" + KindDeniedByRules Kind = "denied-by-rules" + KindDeniedInteractivelyByUser Kind = "denied-interactively-by-user" + KindDeniedNoApprovalRuleAndCouldNotRequestFromUser Kind = "denied-no-approval-rule-and-could-not-request-from-user" ) // Log severity level. Determines how the message is displayed in the timeline. Defaults to @@ -555,18 +555,18 @@ const ( type Level string const ( - Error Level = "error" - Info Level = "info" - Warning Level = "warning" + LevelError Level = "error" + LevelInfo Level = "info" + LevelWarning Level = "warning" ) // Signal to send (default: SIGTERM) type Signal string const ( - Sigint Signal = "SIGINT" - Sigkill Signal = "SIGKILL" - Sigterm Signal = "SIGTERM" + SignalSIGINT Signal = "SIGINT" + SignalSIGKILL Signal = "SIGKILL" + SignalSIGTERM Signal = "SIGTERM" ) type ResultUnion struct { @@ -972,25 +972,25 @@ func (a *SkillsRpcApi) Reload(ctx context.Context) (*SessionSkillsReloadResult, return &result, nil } -type McpRpcApi struct { +type MCPRpcApi struct { client *jsonrpc2.Client sessionID string } -func (a *McpRpcApi) List(ctx context.Context) (*SessionMcpListResult, error) { +func (a *MCPRpcApi) List(ctx context.Context) (*SessionMCPListResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} raw, err := a.client.Request("session.mcp.list", req) if err != nil { return nil, err } - var result SessionMcpListResult + var result SessionMCPListResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *McpRpcApi) Enable(ctx context.Context, params *SessionMcpEnableParams) (*SessionMcpEnableResult, error) { +func (a *MCPRpcApi) Enable(ctx context.Context, params *SessionMCPEnableParams) (*SessionMCPEnableResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} if params != nil { req["serverName"] = params.ServerName @@ -999,14 +999,14 @@ func (a *McpRpcApi) Enable(ctx context.Context, params *SessionMcpEnableParams) if err != nil { return nil, err } - var result SessionMcpEnableResult + var result SessionMCPEnableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *McpRpcApi) Disable(ctx context.Context, params *SessionMcpDisableParams) (*SessionMcpDisableResult, error) { +func (a *MCPRpcApi) Disable(ctx context.Context, params *SessionMCPDisableParams) (*SessionMCPDisableResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} if params != nil { req["serverName"] = params.ServerName @@ -1015,20 +1015,20 @@ func (a *McpRpcApi) Disable(ctx context.Context, params *SessionMcpDisableParams if err != nil { return nil, err } - var result SessionMcpDisableResult + var result SessionMCPDisableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *McpRpcApi) Reload(ctx context.Context) (*SessionMcpReloadResult, error) { +func (a *MCPRpcApi) Reload(ctx context.Context) (*SessionMCPReloadResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} raw, err := a.client.Request("session.mcp.reload", req) if err != nil { return nil, err } - var result SessionMcpReloadResult + var result SessionMCPReloadResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1240,7 +1240,7 @@ type SessionRpc struct { Fleet *FleetRpcApi Agent *AgentRpcApi Skills *SkillsRpcApi - Mcp *McpRpcApi + MCP *MCPRpcApi Plugins *PluginsRpcApi Extensions *ExtensionsRpcApi Compaction *CompactionRpcApi @@ -1283,7 +1283,7 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { Fleet: &FleetRpcApi{client: client, sessionID: sessionID}, Agent: &AgentRpcApi{client: client, sessionID: sessionID}, Skills: &SkillsRpcApi{client: client, sessionID: sessionID}, - Mcp: &McpRpcApi{client: client, sessionID: sessionID}, + MCP: &MCPRpcApi{client: client, sessionID: sessionID}, Plugins: &PluginsRpcApi{client: client, sessionID: sessionID}, Extensions: &ExtensionsRpcApi{client: client, sessionID: sessionID}, Compaction: &CompactionRpcApi{client: client, sessionID: sessionID}, diff --git a/go/session.go b/go/session.go index 587aacfa9..107ac9824 100644 --- a/go/session.go +++ b/go/session.go @@ -544,8 +544,8 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, if r := recover(); r != nil { errMsg := fmt.Sprintf("tool panic: %v", r) s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.SessionToolsHandlePendingToolCallParams{ - RequestID: requestID, - LevelError: &errMsg, + RequestID: requestID, + Error: &errMsg, }) } }() @@ -562,8 +562,8 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, if err != nil { errMsg := err.Error() s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.SessionToolsHandlePendingToolCallParams{ - RequestID: requestID, - LevelError: &errMsg, + RequestID: requestID, + Error: &errMsg, }) return } diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 1ebc50797..7a0dbd7e8 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -26,7 +26,7 @@ const execFileAsync = promisify(execFile); // ── Utilities ─────────────────────────────────────────────────────────────── // Go initialisms that should be all-caps -const goInitialisms = new Set(["id", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc"]); +const goInitialisms = new Set(["id", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc", "mcp"]); function toPascalCase(s: string): string { return s @@ -44,6 +44,77 @@ function toGoFieldName(jsonName: string): string { .join(""); } +/** + * Post-process Go enum constants so every constant follows the canonical + * Go `TypeNameValue` convention. quicktype disambiguates collisions with + * whimsical prefixes (Purple, Fluffy, …) that we replace. + */ +function postProcessEnumConstants(code: string): string { + const renames = new Map(); + + // Match constant declarations inside const ( … ) blocks. + const constLineRe = /^\s+(\w+)\s+(\w+)\s*=\s*"([^"]+)"/gm; + let m; + while ((m = constLineRe.exec(code)) !== null) { + const [, constName, typeName, value] = m; + if (constName.startsWith(typeName)) continue; + + const valuePascal = value + .split(/[._-]/) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(""); + const desired = typeName + valuePascal; + if (constName !== desired) { + renames.set(constName, desired); + } + } + + // Replace each const block in place, then fix switch-case references + // in marshal/unmarshal functions. This avoids renaming struct fields. + + // Phase 1: Rename inside const ( … ) blocks + code = code.replace(/^(const \([\s\S]*?\n\))/gm, (block) => { + let b = block; + for (const [oldName, newName] of renames) { + b = b.replace(new RegExp(`\\b${oldName}\\b`, "g"), newName); + } + return b; + }); + + // Phase 2: Rename inside func bodies that reference the enum constants + // (marshal/unmarshal helpers generated by quicktype use case statements) + code = code.replace(/^(func \(.*?\n\})/gm, (funcBlock) => { + let b = funcBlock; + for (const [oldName, newName] of renames) { + b = b.replace(new RegExp(`\\b${oldName}\\b`, "g"), newName); + } + return b; + }); + + return code; +} + +/** + * Extract a mapping from (structName, jsonFieldName) → goFieldName + * so the wrapper code references the actual quicktype-generated field names. + */ +function extractFieldNames(qtCode: string): Map> { + const result = new Map>(); + const structRe = /^type\s+(\w+)\s+struct\s*\{([^}]*)\}/gm; + let sm; + while ((sm = structRe.exec(qtCode)) !== null) { + const [, structName, body] = sm; + const fields = new Map(); + const fieldRe = /^\s+(\w+)\s+[^`\n]+`json:"([^",]+)/gm; + let fm; + while ((fm = fieldRe.exec(body)) !== null) { + fields.set(fm[2], fm[1]); + } + result.set(structName, fields); + } + return result; +} + async function formatGoFile(filePath: string): Promise { try { await execFileAsync("go", ["fmt", filePath]); @@ -92,7 +163,7 @@ async function generateSessionEvents(schemaPath?: string): Promise { `; - const outPath = await writeGeneratedFile("go/generated_session_events.go", banner + result.lines.join("\n")); + const outPath = await writeGeneratedFile("go/generated_session_events.go", banner + postProcessEnumConstants(result.lines.join("\n"))); console.log(` ✓ ${outPath}`); await formatGoFile(outPath); @@ -153,6 +224,22 @@ async function generateRpc(schemaPath?: string): Promise { rendererOptions: { package: "copilot", "just-types": "true" }, }); + // Post-process quicktype output: fix enum constant names + let qtCode = qtResult.lines.filter((l) => !l.startsWith("package ")).join("\n"); + qtCode = postProcessEnumConstants(qtCode); + + // Extract actual type names generated by quicktype (may differ from toPascalCase) + const actualTypeNames = new Map(); + const structRe = /^type\s+(\w+)\s+struct\b/gm; + let sm; + while ((sm = structRe.exec(qtCode)) !== null) { + actualTypeNames.set(sm[1].toLowerCase(), sm[1]); + } + const resolveType = (name: string): string => actualTypeNames.get(name.toLowerCase()) ?? name; + + // Extract field name mappings (quicktype may rename fields to avoid Go keyword conflicts) + const fieldNames = extractFieldNames(qtCode); + // Build method wrappers const lines: string[] = []; lines.push(`// AUTO-GENERATED FILE - DO NOT EDIT`); @@ -168,19 +255,18 @@ async function generateRpc(schemaPath?: string): Promise { lines.push(`)`); lines.push(``); - // Add quicktype-generated types (skip package line) - const qtLines = qtResult.lines.filter((l) => !l.startsWith("package ")); - lines.push(...qtLines); + // Add quicktype-generated types + lines.push(qtCode); lines.push(``); // Emit ServerRpc if (schema.server) { - emitRpcWrapper(lines, schema.server, false); + emitRpcWrapper(lines, schema.server, false, resolveType, fieldNames); } // Emit SessionRpc if (schema.session) { - emitRpcWrapper(lines, schema.session, true); + emitRpcWrapper(lines, schema.session, true, resolveType, fieldNames); } const outPath = await writeGeneratedFile("go/rpc/generated_rpc.go", lines.join("\n")); @@ -189,7 +275,7 @@ async function generateRpc(schemaPath?: string): Promise { await formatGoFile(outPath); } -function emitRpcWrapper(lines: string[], node: Record, isSession: boolean): void { +function emitRpcWrapper(lines: string[], node: Record, isSession: boolean, resolveType: (name: string) => string, fieldNames: Map>): void { const groups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); const topLevelMethods = Object.entries(node).filter(([, v]) => isRpcMethod(v)); @@ -205,7 +291,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio lines.push(``); for (const [key, value] of Object.entries(groupNode as Record)) { if (!isRpcMethod(value)) continue; - emitMethod(lines, apiName, key, value, isSession); + emitMethod(lines, apiName, key, value, isSession, resolveType, fieldNames); } } @@ -224,7 +310,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio // Top-level methods (server only) for (const [key, value] of topLevelMethods) { if (!isRpcMethod(value)) continue; - emitMethod(lines, wrapperName, key, value, isSession); + emitMethod(lines, wrapperName, key, value, isSession, resolveType, fieldNames); } // Constructor @@ -244,15 +330,15 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio lines.push(``); } -function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean): void { +function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, fieldNames: Map>): void { const methodName = toPascalCase(name); - const resultType = toPascalCase(method.rpcMethod) + "Result"; + const resultType = resolveType(toPascalCase(method.rpcMethod) + "Result"); const paramProps = method.params?.properties || {}; const requiredParams = new Set(method.params?.required || []); const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0; - const paramsType = hasParams ? toPascalCase(method.rpcMethod) + "Params" : ""; + const paramsType = hasParams ? resolveType(toPascalCase(method.rpcMethod) + "Params") : ""; const sig = hasParams ? `func (a *${receiver}) ${methodName}(ctx context.Context, params *${paramsType}) (*${resultType}, error)` @@ -265,7 +351,7 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc if (hasParams) { lines.push(` if params != nil {`); for (const pName of nonSessionParams) { - const goField = toGoFieldName(pName); + const goField = fieldNames.get(paramsType)?.get(pName) ?? toGoFieldName(pName); const isOptional = !requiredParams.has(pName); if (isOptional) { // Optional fields are pointers - only add when non-nil and dereference From 764619d4c5da798eb5bbe803325ce5cbebe8d386 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 10:28:04 -0700 Subject: [PATCH 03/11] docs: update Go image-input examples to use copilot.AttachmentTypeFile The generated enum constant was renamed from copilot.File to copilot.AttachmentTypeFile to follow Go's TypeNameValue convention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/image-input.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/features/image-input.md b/docs/features/image-input.md index aa3bf2f64..147a5258f 100644 --- a/docs/features/image-input.md +++ b/docs/features/image-input.md @@ -117,7 +117,7 @@ func main() { Prompt: "Describe what you see in this image", Attachments: []copilot.Attachment{ { - Type: copilot.File, + Type: copilot.AttachmentTypeFile, Path: &path, }, }, @@ -143,7 +143,7 @@ session.Send(ctx, copilot.MessageOptions{ Prompt: "Describe what you see in this image", Attachments: []copilot.Attachment{ { - Type: copilot.File, + Type: copilot.AttachmentTypeFile, Path: &path, }, }, From ae36c6b1d666821fd75fb4fe8ae5ebbf4033b552 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 10:29:12 -0700 Subject: [PATCH 04/11] fix(codegen): resolve Python type names from quicktype output for acronyms The Python codegen used toPascalCase() to compute type names like SessionMcpListResult, but quicktype generates SessionMCPListResult (uppercase MCP). This caused runtime NameError in Python scenarios. Apply the same approach as go.ts: after quicktype runs, parse the generated output to extract actual class names and build a case-insensitive lookup map. Use resolveType() in emitMethod() and emitRpcWrapper() instead of recomputing names via toPascalCase(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/copilot/generated/rpc.py | 16 ++++++++-------- scripts/codegen/python.ts | 26 ++++++++++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 1bc431ee6..4cdd3ebc7 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -2393,21 +2393,21 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, *, timeout: float | None = None) -> SessionMcpListResult: - return SessionMcpListResult.from_dict(await self._client.request("session.mcp.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list(self, *, timeout: float | None = None) -> SessionMCPListResult: + return SessionMCPListResult.from_dict(await self._client.request("session.mcp.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def enable(self, params: SessionMcpEnableParams, *, timeout: float | None = None) -> SessionMcpEnableResult: + async def enable(self, params: SessionMCPEnableParams, *, timeout: float | None = None) -> SessionMCPEnableResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionMcpEnableResult.from_dict(await self._client.request("session.mcp.enable", params_dict, **_timeout_kwargs(timeout))) + return SessionMCPEnableResult.from_dict(await self._client.request("session.mcp.enable", params_dict, **_timeout_kwargs(timeout))) - async def disable(self, params: SessionMcpDisableParams, *, timeout: float | None = None) -> SessionMcpDisableResult: + async def disable(self, params: SessionMCPDisableParams, *, timeout: float | None = None) -> SessionMCPDisableResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionMcpDisableResult.from_dict(await self._client.request("session.mcp.disable", params_dict, **_timeout_kwargs(timeout))) + return SessionMCPDisableResult.from_dict(await self._client.request("session.mcp.disable", params_dict, **_timeout_kwargs(timeout))) - async def reload(self, *, timeout: float | None = None) -> SessionMcpReloadResult: - return SessionMcpReloadResult.from_dict(await self._client.request("session.mcp.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def reload(self, *, timeout: float | None = None) -> SessionMCPReloadResult: + return SessionMCPReloadResult.from_dict(await self._client.request("session.mcp.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) class PluginsApi: diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 65563d741..6e2dd5dba 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -215,6 +215,16 @@ async function generateRpc(schemaPath?: string): Promise { // Modernize to Python 3.11+ syntax typesCode = modernizePython(typesCode); + // Extract actual class names generated by quicktype (may differ from toPascalCase, + // e.g. quicktype produces "SessionMCPList" not "SessionMcpList") + const actualTypeNames = new Map(); + const classRe = /^class\s+(\w+)\b/gm; + let cm; + while ((cm = classRe.exec(typesCode)) !== null) { + actualTypeNames.set(cm[1].toLowerCase(), cm[1]); + } + const resolveType = (name: string): string => actualTypeNames.get(name.toLowerCase()) ?? name; + const lines: string[] = []; lines.push(`""" AUTO-GENERATED FILE - DO NOT EDIT @@ -239,17 +249,17 @@ def _timeout_kwargs(timeout: float | None) -> dict: // Emit RPC wrapper classes if (schema.server) { - emitRpcWrapper(lines, schema.server, false); + emitRpcWrapper(lines, schema.server, false, resolveType); } if (schema.session) { - emitRpcWrapper(lines, schema.session, true); + emitRpcWrapper(lines, schema.session, true, resolveType); } const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", lines.join("\n")); console.log(` ✓ ${outPath}`); } -function emitRpcWrapper(lines: string[], node: Record, isSession: boolean): void { +function emitRpcWrapper(lines: string[], node: Record, isSession: boolean, resolveType: (name: string) => string): void { const groups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); const topLevelMethods = Object.entries(node).filter(([, v]) => isRpcMethod(v)); @@ -272,7 +282,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio lines.push(``); for (const [key, value] of Object.entries(groupNode as Record)) { if (!isRpcMethod(value)) continue; - emitMethod(lines, key, value, isSession); + emitMethod(lines, key, value, isSession, resolveType); } lines.push(``); } @@ -301,19 +311,19 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio // Top-level methods for (const [key, value] of topLevelMethods) { if (!isRpcMethod(value)) continue; - emitMethod(lines, key, value, isSession); + emitMethod(lines, key, value, isSession, resolveType); } lines.push(``); } -function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean): void { +function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string): void { const methodName = toSnakeCase(name); - const resultType = toPascalCase(method.rpcMethod) + "Result"; + const resultType = resolveType(toPascalCase(method.rpcMethod) + "Result"); const paramProps = method.params?.properties || {}; const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0; - const paramsType = toPascalCase(method.rpcMethod) + "Params"; + const paramsType = resolveType(toPascalCase(method.rpcMethod) + "Params"); // Build signature with typed params + optional timeout const sig = hasParams From f6d324efdcd9496de5288432db3bfac7c8218d68 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 10:29:28 -0700 Subject: [PATCH 05/11] fix: update Go E2E tests for agent list and multi-client timeout - TestAgentSelectionRpc: CLI now returns built-in agents, so instead of asserting zero agents, verify no custom agents appear when none configured - TestMultiClient: increase broadcast event timeout from 10s to 30s Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/internal/e2e/agent_and_compact_rpc_test.go | 11 ++++++++--- go/internal/e2e/multi_client_test.go | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/go/internal/e2e/agent_and_compact_rpc_test.go b/go/internal/e2e/agent_and_compact_rpc_test.go index 338f4da67..cbd52a326 100644 --- a/go/internal/e2e/agent_and_compact_rpc_test.go +++ b/go/internal/e2e/agent_and_compact_rpc_test.go @@ -215,7 +215,7 @@ func TestAgentSelectionRpc(t *testing.T) { } }) - t.Run("should return empty list when no custom agents configured", func(t *testing.T) { + t.Run("should return no custom agents when none configured", func(t *testing.T) { client := copilot.NewClient(&copilot.ClientOptions{ CLIPath: cliPath, UseStdio: copilot.Bool(true), @@ -238,8 +238,13 @@ func TestAgentSelectionRpc(t *testing.T) { t.Fatalf("Failed to list agents: %v", err) } - if len(result.Agents) != 0 { - t.Errorf("Expected empty agent list, got %d agents", len(result.Agents)) + // The CLI may return built-in/default agents even when no custom agents + // are configured, so just verify none of the known custom agent names appear. + customNames := map[string]bool{"test-agent": true, "another-agent": true} + for _, agent := range result.Agents { + if customNames[agent.Name] { + t.Errorf("Expected no custom agents, but found %q", agent.Name) + } } if err := client.Stop(); err != nil { diff --git a/go/internal/e2e/multi_client_test.go b/go/internal/e2e/multi_client_test.go index bc3f757f0..3c7dc34c3 100644 --- a/go/internal/e2e/multi_client_test.go +++ b/go/internal/e2e/multi_client_test.go @@ -120,7 +120,7 @@ func TestMultiClient(t *testing.T) { } // Wait for all broadcast events to arrive on both clients - timeout := time.After(10 * time.Second) + timeout := time.After(30 * time.Second) for _, ch := range []chan struct{}{client1Requested, client2Requested, client1Completed, client2Completed} { select { case <-ch: From 3a73bedd7150bcf810887b67dd6f2f7361681a38 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 10:33:24 -0700 Subject: [PATCH 06/11] Fix LogAsync compilation error by adding missing url parameter The generated Rpc.LogAsync method added a 'url' parameter between 'ephemeral' and 'cancellationToken', causing a type mismatch when Session.LogAsync passed cancellationToken as the 4th positional arg. Added the 'url' parameter to Session.LogAsync to match the generated Rpc method signature and pass it through correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Session.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 606c0b052..0014ec7f0 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -749,6 +749,7 @@ public Task SetModelAsync(string model, CancellationToken cancellationToken = de /// The message to log. /// Log level (default: info). /// When true, the message is not persisted to disk. + /// Optional URL to associate with the log entry. /// Optional cancellation token. /// /// @@ -758,9 +759,9 @@ public Task SetModelAsync(string model, CancellationToken cancellationToken = de /// await session.LogAsync("Temporary status", ephemeral: true); /// /// - public async Task LogAsync(string message, SessionLogRequestLevel? level = null, bool? ephemeral = null, CancellationToken cancellationToken = default) + public async Task LogAsync(string message, SessionLogRequestLevel? level = null, bool? ephemeral = null, string? url = null, CancellationToken cancellationToken = default) { - await Rpc.LogAsync(message, level, ephemeral, cancellationToken); + await Rpc.LogAsync(message, level, ephemeral, url, cancellationToken); } /// From 683128228f05d60e843772120e54a38a15b0b031 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 10:35:01 -0700 Subject: [PATCH 07/11] fix: address review comments - valuePascal initialisms, Phase 2 regex, add pr/ado - valuePascal now uses goInitialisms so 'url' -> 'URL', 'mcp' -> 'MCP', etc. - Phase 2 regex uses [\s\S]*? to match multi-line func bodies - Added 'pr' and 'ado' to goInitialisms Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/generated_session_events.go | 12 ++++++------ scripts/codegen/go.ts | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go/generated_session_events.go b/go/generated_session_events.go index ecc1a775e..4bf52df93 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -1255,7 +1255,7 @@ type ReferenceType string const ( ReferenceTypeDiscussion ReferenceType = "discussion" ReferenceTypeIssue ReferenceType = "issue" - ReferenceTypePr ReferenceType = "pr" + ReferenceTypePR ReferenceType = "pr" ) type AttachmentType string @@ -1272,7 +1272,7 @@ const ( type HostType string const ( - HostTypeAdo HostType = "ado" + HostTypeADO HostType = "ado" HostTypeGithub HostType = "github" ) @@ -1334,10 +1334,10 @@ const ( PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" PermissionRequestKindHook PermissionRequestKind = "hook" PermissionRequestKindShell PermissionRequestKind = "shell" - PermissionRequestKindMcp PermissionRequestKind = "mcp" + PermissionRequestKindMCP PermissionRequestKind = "mcp" PermissionRequestKindMemory PermissionRequestKind = "memory" PermissionRequestKindRead PermissionRequestKind = "read" - PermissionRequestKindUrl PermissionRequestKind = "url" + PermissionRequestKindURL PermissionRequestKind = "url" PermissionRequestKindWrite PermissionRequestKind = "write" ) @@ -1458,8 +1458,8 @@ const ( SessionEventTypeSessionHandoff SessionEventType = "session.handoff" SessionEventTypeSessionIdle SessionEventType = "session.idle" SessionEventTypeSessionInfo SessionEventType = "session.info" - SessionEventTypeSessionMcpServerStatusChanged SessionEventType = "session.mcp_server_status_changed" - SessionEventTypeSessionMcpServersLoaded SessionEventType = "session.mcp_servers_loaded" + SessionEventTypeSessionMCPServerStatusChanged SessionEventType = "session.mcp_server_status_changed" + SessionEventTypeSessionMCPServersLoaded SessionEventType = "session.mcp_servers_loaded" SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed" SessionEventTypeSessionModelChange SessionEventType = "session.model_change" SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed" diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 7a0dbd7e8..68f1b3ecd 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -26,7 +26,7 @@ const execFileAsync = promisify(execFile); // ── Utilities ─────────────────────────────────────────────────────────────── // Go initialisms that should be all-caps -const goInitialisms = new Set(["id", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc", "mcp"]); +const goInitialisms = new Set(["id", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc", "mcp", "pr", "ado"]); function toPascalCase(s: string): string { return s @@ -59,9 +59,10 @@ function postProcessEnumConstants(code: string): string { const [, constName, typeName, value] = m; if (constName.startsWith(typeName)) continue; + // Use the same initialism logic as toPascalCase so "url" → "URL", "mcp" → "MCP", etc. const valuePascal = value .split(/[._-]/) - .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .map((w) => goInitialisms.has(w.toLowerCase()) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1)) .join(""); const desired = typeName + valuePascal; if (constName !== desired) { @@ -81,9 +82,8 @@ function postProcessEnumConstants(code: string): string { return b; }); - // Phase 2: Rename inside func bodies that reference the enum constants - // (marshal/unmarshal helpers generated by quicktype use case statements) - code = code.replace(/^(func \(.*?\n\})/gm, (funcBlock) => { + // Phase 2: Rename inside func bodies (marshal/unmarshal helpers use case statements) + code = code.replace(/^(func \([\s\S]*?\n\})/gm, (funcBlock) => { let b = funcBlock; for (const [oldName, newName] of renames) { b = b.replace(new RegExp(`\\b${oldName}\\b`, "g"), newName); From d9d07ceeb543e0d4134019c10c91131c6e771013 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 11:04:10 -0700 Subject: [PATCH 08/11] Fix Python E2E agent list test for built-in agents The CLI now returns built-in/default agents even when no custom agents are configured. Update the assertion to verify no custom test agent names appear in the list, rather than asserting the list is empty. Matches the pattern used in the Go E2E test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/e2e/test_agent_and_compact_rpc.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python/e2e/test_agent_and_compact_rpc.py b/python/e2e/test_agent_and_compact_rpc.py index ec5958676..0f585c81b 100644 --- a/python/e2e/test_agent_and_compact_rpc.py +++ b/python/e2e/test_agent_and_compact_rpc.py @@ -155,7 +155,7 @@ async def test_should_deselect_current_agent(self): @pytest.mark.asyncio async def test_should_return_empty_list_when_no_custom_agents_configured(self): - """Test listing agents returns empty when none configured.""" + """Test listing agents returns no custom agents when none configured.""" client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True)) try: @@ -165,7 +165,13 @@ async def test_should_return_empty_list_when_no_custom_agents_configured(self): ) result = await session.rpc.agent.list() - assert result.agents == [] + # The CLI may return built-in/default agents even when no custom agents + # are configured. Verify no custom test agents appear in the list. + custom_names = {"test-agent", "another-agent"} + for agent in result.agents: + assert agent.name not in custom_names, ( + f"Expected no custom agents, but found {agent.name!r}" + ) await session.disconnect() await client.stop() From 550ba80c9365f34c8a8460f0da20f205c95412e9 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 11:04:19 -0700 Subject: [PATCH 09/11] Skip multi-client broadcast test across Go, Python, and C# CLI 1.0.7 no longer delivers broadcast external_tool events to secondary clients. Skip the 'both clients see tool request and completion events' test in all three languages with a clear note. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/test/MultiClientTests.cs | 2 +- go/internal/e2e/multi_client_test.go | 1 + python/e2e/test_multi_client.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dotnet/test/MultiClientTests.cs b/dotnet/test/MultiClientTests.cs index bdd264a4a..45cac2d03 100644 --- a/dotnet/test/MultiClientTests.cs +++ b/dotnet/test/MultiClientTests.cs @@ -92,7 +92,7 @@ public async Task DisposeAsync() private CopilotClient Client2 => _client2 ?? throw new InvalidOperationException("Client2 not initialized"); - [Fact] + [Fact(Skip = "CLI 1.0.7 no longer broadcasts external_tool events to secondary clients")] public async Task Both_Clients_See_Tool_Request_And_Completion_Events() { var tool = AIFunctionFactory.Create(MagicNumber, "magic_number"); diff --git a/go/internal/e2e/multi_client_test.go b/go/internal/e2e/multi_client_test.go index 3c7dc34c3..fdbf79b9c 100644 --- a/go/internal/e2e/multi_client_test.go +++ b/go/internal/e2e/multi_client_test.go @@ -44,6 +44,7 @@ func TestMultiClient(t *testing.T) { t.Cleanup(func() { client2.ForceStop() }) t.Run("both clients see tool request and completion events", func(t *testing.T) { + t.Skip("Skipped: CLI 1.0.7 no longer broadcasts external_tool events to secondary clients") ctx.ConfigureForTest(t) type SeedParams struct { diff --git a/python/e2e/test_multi_client.py b/python/e2e/test_multi_client.py index cb5d90cd2..6395e6035 100644 --- a/python/e2e/test_multi_client.py +++ b/python/e2e/test_multi_client.py @@ -189,6 +189,7 @@ async def test_both_clients_see_tool_request_and_completion_events( self, mctx: MultiClientContext ): """Both clients see tool request and completion events.""" + pytest.skip("CLI 1.0.7 no longer broadcasts external_tool events to secondary clients") class SeedParams(BaseModel): seed: str = Field(description="A seed value") From 08ef0c47a6158489a696909c41833dd6eec56f55 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 11:59:01 -0700 Subject: [PATCH 10/11] Revert multi-client broadcast test skips for CLI 1.0.7 Remove the t.Skip/pytest.skip/Fact(Skip=...) additions that were disabling the multi-client broadcast tests across Go, Python, and C#. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/test/MultiClientTests.cs | 2 +- go/internal/e2e/multi_client_test.go | 1 - python/e2e/test_multi_client.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dotnet/test/MultiClientTests.cs b/dotnet/test/MultiClientTests.cs index 45cac2d03..bdd264a4a 100644 --- a/dotnet/test/MultiClientTests.cs +++ b/dotnet/test/MultiClientTests.cs @@ -92,7 +92,7 @@ public async Task DisposeAsync() private CopilotClient Client2 => _client2 ?? throw new InvalidOperationException("Client2 not initialized"); - [Fact(Skip = "CLI 1.0.7 no longer broadcasts external_tool events to secondary clients")] + [Fact] public async Task Both_Clients_See_Tool_Request_And_Completion_Events() { var tool = AIFunctionFactory.Create(MagicNumber, "magic_number"); diff --git a/go/internal/e2e/multi_client_test.go b/go/internal/e2e/multi_client_test.go index fdbf79b9c..3c7dc34c3 100644 --- a/go/internal/e2e/multi_client_test.go +++ b/go/internal/e2e/multi_client_test.go @@ -44,7 +44,6 @@ func TestMultiClient(t *testing.T) { t.Cleanup(func() { client2.ForceStop() }) t.Run("both clients see tool request and completion events", func(t *testing.T) { - t.Skip("Skipped: CLI 1.0.7 no longer broadcasts external_tool events to secondary clients") ctx.ConfigureForTest(t) type SeedParams struct { diff --git a/python/e2e/test_multi_client.py b/python/e2e/test_multi_client.py index 6395e6035..4a6cbc27b 100644 --- a/python/e2e/test_multi_client.py +++ b/python/e2e/test_multi_client.py @@ -189,7 +189,7 @@ async def test_both_clients_see_tool_request_and_completion_events( self, mctx: MultiClientContext ): """Both clients see tool request and completion events.""" - pytest.skip("CLI 1.0.7 no longer broadcasts external_tool events to secondary clients") + class SeedParams(BaseModel): seed: str = Field(description="A seed value") From b4a86616d222aa3a2e69500e8a38076473f3e9b6 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 18 Mar 2026 11:59:29 -0700 Subject: [PATCH 11/11] fix: remove mcp/pr/ado from goInitialisms to avoid SessionRpc.MCP rename resolveType() already handles type name reconciliation from quicktype output, so these initialisms aren't needed and would cause an unnecessary breaking change to the SessionRpc.Mcp field name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/generated_session_events.go | 10 +++++----- go/rpc/generated_rpc.go | 14 +++++++------- scripts/codegen/go.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 4bf52df93..d68436e8d 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -1255,7 +1255,7 @@ type ReferenceType string const ( ReferenceTypeDiscussion ReferenceType = "discussion" ReferenceTypeIssue ReferenceType = "issue" - ReferenceTypePR ReferenceType = "pr" + ReferenceTypePr ReferenceType = "pr" ) type AttachmentType string @@ -1272,7 +1272,7 @@ const ( type HostType string const ( - HostTypeADO HostType = "ado" + HostTypeAdo HostType = "ado" HostTypeGithub HostType = "github" ) @@ -1334,7 +1334,7 @@ const ( PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" PermissionRequestKindHook PermissionRequestKind = "hook" PermissionRequestKindShell PermissionRequestKind = "shell" - PermissionRequestKindMCP PermissionRequestKind = "mcp" + PermissionRequestKindMcp PermissionRequestKind = "mcp" PermissionRequestKindMemory PermissionRequestKind = "memory" PermissionRequestKindRead PermissionRequestKind = "read" PermissionRequestKindURL PermissionRequestKind = "url" @@ -1458,8 +1458,8 @@ const ( SessionEventTypeSessionHandoff SessionEventType = "session.handoff" SessionEventTypeSessionIdle SessionEventType = "session.idle" SessionEventTypeSessionInfo SessionEventType = "session.info" - SessionEventTypeSessionMCPServerStatusChanged SessionEventType = "session.mcp_server_status_changed" - SessionEventTypeSessionMCPServersLoaded SessionEventType = "session.mcp_servers_loaded" + SessionEventTypeSessionMcpServerStatusChanged SessionEventType = "session.mcp_server_status_changed" + SessionEventTypeSessionMcpServersLoaded SessionEventType = "session.mcp_servers_loaded" SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed" SessionEventTypeSessionModelChange SessionEventType = "session.model_change" SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed" diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 6579ab695..b6d171bfa 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -972,12 +972,12 @@ func (a *SkillsRpcApi) Reload(ctx context.Context) (*SessionSkillsReloadResult, return &result, nil } -type MCPRpcApi struct { +type McpRpcApi struct { client *jsonrpc2.Client sessionID string } -func (a *MCPRpcApi) List(ctx context.Context) (*SessionMCPListResult, error) { +func (a *McpRpcApi) List(ctx context.Context) (*SessionMCPListResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} raw, err := a.client.Request("session.mcp.list", req) if err != nil { @@ -990,7 +990,7 @@ func (a *MCPRpcApi) List(ctx context.Context) (*SessionMCPListResult, error) { return &result, nil } -func (a *MCPRpcApi) Enable(ctx context.Context, params *SessionMCPEnableParams) (*SessionMCPEnableResult, error) { +func (a *McpRpcApi) Enable(ctx context.Context, params *SessionMCPEnableParams) (*SessionMCPEnableResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} if params != nil { req["serverName"] = params.ServerName @@ -1006,7 +1006,7 @@ func (a *MCPRpcApi) Enable(ctx context.Context, params *SessionMCPEnableParams) return &result, nil } -func (a *MCPRpcApi) Disable(ctx context.Context, params *SessionMCPDisableParams) (*SessionMCPDisableResult, error) { +func (a *McpRpcApi) Disable(ctx context.Context, params *SessionMCPDisableParams) (*SessionMCPDisableResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} if params != nil { req["serverName"] = params.ServerName @@ -1022,7 +1022,7 @@ func (a *MCPRpcApi) Disable(ctx context.Context, params *SessionMCPDisableParams return &result, nil } -func (a *MCPRpcApi) Reload(ctx context.Context) (*SessionMCPReloadResult, error) { +func (a *McpRpcApi) Reload(ctx context.Context) (*SessionMCPReloadResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} raw, err := a.client.Request("session.mcp.reload", req) if err != nil { @@ -1240,7 +1240,7 @@ type SessionRpc struct { Fleet *FleetRpcApi Agent *AgentRpcApi Skills *SkillsRpcApi - MCP *MCPRpcApi + Mcp *McpRpcApi Plugins *PluginsRpcApi Extensions *ExtensionsRpcApi Compaction *CompactionRpcApi @@ -1283,7 +1283,7 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { Fleet: &FleetRpcApi{client: client, sessionID: sessionID}, Agent: &AgentRpcApi{client: client, sessionID: sessionID}, Skills: &SkillsRpcApi{client: client, sessionID: sessionID}, - MCP: &MCPRpcApi{client: client, sessionID: sessionID}, + Mcp: &McpRpcApi{client: client, sessionID: sessionID}, Plugins: &PluginsRpcApi{client: client, sessionID: sessionID}, Extensions: &ExtensionsRpcApi{client: client, sessionID: sessionID}, Compaction: &CompactionRpcApi{client: client, sessionID: sessionID}, diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 68f1b3ecd..21c329eb1 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -26,7 +26,7 @@ const execFileAsync = promisify(execFile); // ── Utilities ─────────────────────────────────────────────────────────────── // Go initialisms that should be all-caps -const goInitialisms = new Set(["id", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc", "mcp", "pr", "ado"]); +const goInitialisms = new Set(["id", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc"]); function toPascalCase(s: string): string { return s