From 9bf6199c5f01afebafda9af5dbe305c4ba868cca Mon Sep 17 00:00:00 2001 From: Sai Sankar Gochhayat Date: Fri, 20 Feb 2026 12:59:50 -0800 Subject: [PATCH] fix(providers): fix GitHub Copilot provider session lifecycle and response handling - Store client as struct field to prevent premature garbage collection - Remove defer client.Stop() from constructor which was killing the gRPC client immediately after NewGitHubCopilotProvider returned, causing all subsequent Chat() calls to silently fail - Add proper error handling for CreateSession (was ignoring error with _) - Replace session.Send() with session.SendAndWait(): Send() only returns a message UUID, not the response text; SendAndWait() blocks until the assistant is idle and returns the actual SessionEvent with content - Add Close() method for proper client lifecycle management - Add 15s connection timeout to fail fast if CLI is unreachable --- pkg/providers/github_copilot_provider.go | 53 +++++++++++++++++------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/pkg/providers/github_copilot_provider.go b/pkg/providers/github_copilot_provider.go index 6124881f7..d6a745c40 100644 --- a/pkg/providers/github_copilot_provider.go +++ b/pkg/providers/github_copilot_provider.go @@ -2,56 +2,70 @@ package providers import ( "context" - "encoding/json" "fmt" + "time" + + json "encoding/json" copilot "github.com/github/copilot-sdk/go" ) type GitHubCopilotProvider struct { uri string - connectMode string // `stdio` or `grpc`` + connectMode string // `stdio` or `grpc` + client *copilot.Client session *copilot.Session } func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*GitHubCopilotProvider, error) { + var session *copilot.Session + var client *copilot.Client if connectMode == "" { connectMode = "grpc" } switch connectMode { case "stdio": - // todo + //todo case "grpc": - client := copilot.NewClient(&copilot.ClientOptions{ + client = copilot.NewClient(&copilot.ClientOptions{ CLIUrl: uri, }) - if err := client.Start(context.Background()); err != nil { - return nil, fmt.Errorf( - "Can't connect to Github Copilot, https://github.com/github/copilot-sdk/blob/main/docs/getting-started.md#connecting-to-an-external-cli-server for details", - ) + connectCtx, connectCancel := context.WithTimeout(context.Background(), 15*time.Second) + defer connectCancel() + if err := client.Start(connectCtx); err != nil { + return nil, fmt.Errorf("can't connect to Github Copilot: %w", err) } - defer client.Stop() - session, _ = client.CreateSession(context.Background(), &copilot.SessionConfig{ + var err error + session, err = client.CreateSession(connectCtx, &copilot.SessionConfig{ Model: model, Hooks: &copilot.SessionHooks{}, }) + if err != nil { + client.Stop() + return nil, fmt.Errorf("failed to create Copilot session: %w", err) + } } return &GitHubCopilotProvider{ uri: uri, connectMode: connectMode, + client: client, session: session, }, nil } +func (p *GitHubCopilotProvider) Close() { + if p.client != nil { + p.client.Stop() + } +} + // Chat sends a chat request to GitHub Copilot -func (p *GitHubCopilotProvider) Chat( - ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any, -) (*LLMResponse, error) { +func (p *GitHubCopilotProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { type tempMessage struct { Role string `json:"role"` Content string `json:"content"` @@ -67,16 +81,25 @@ func (p *GitHubCopilotProvider) Chat( fullcontent, _ := json.Marshal(out) - content, _ := p.session.Send(ctx, copilot.MessageOptions{ + event, err := p.session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: string(fullcontent), }) + if err != nil { + return nil, fmt.Errorf("copilot error: %w", err) + } + + if event == nil || event.Data.Content == nil { + return nil, fmt.Errorf("empty response from Copilot") + } return &LLMResponse{ FinishReason: "stop", - Content: content, + Content: *event.Data.Content, }, nil + } func (p *GitHubCopilotProvider) GetDefaultModel() string { + return "gpt-4.1" }