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
This commit is contained in:
parent
e883e14b81
commit
9bf6199c5f
1 changed files with 38 additions and 15 deletions
|
|
@ -2,56 +2,70 @@ package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
json "encoding/json"
|
||||||
|
|
||||||
copilot "github.com/github/copilot-sdk/go"
|
copilot "github.com/github/copilot-sdk/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitHubCopilotProvider struct {
|
type GitHubCopilotProvider struct {
|
||||||
uri string
|
uri string
|
||||||
connectMode string // `stdio` or `grpc``
|
connectMode string // `stdio` or `grpc`
|
||||||
|
|
||||||
|
client *copilot.Client
|
||||||
session *copilot.Session
|
session *copilot.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*GitHubCopilotProvider, error) {
|
func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*GitHubCopilotProvider, error) {
|
||||||
|
|
||||||
var session *copilot.Session
|
var session *copilot.Session
|
||||||
|
var client *copilot.Client
|
||||||
if connectMode == "" {
|
if connectMode == "" {
|
||||||
connectMode = "grpc"
|
connectMode = "grpc"
|
||||||
}
|
}
|
||||||
switch connectMode {
|
switch connectMode {
|
||||||
|
|
||||||
case "stdio":
|
case "stdio":
|
||||||
// todo
|
//todo
|
||||||
case "grpc":
|
case "grpc":
|
||||||
client := copilot.NewClient(&copilot.ClientOptions{
|
client = copilot.NewClient(&copilot.ClientOptions{
|
||||||
CLIUrl: uri,
|
CLIUrl: uri,
|
||||||
})
|
})
|
||||||
if err := client.Start(context.Background()); err != nil {
|
connectCtx, connectCancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
return nil, fmt.Errorf(
|
defer connectCancel()
|
||||||
"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",
|
if err := client.Start(connectCtx); err != nil {
|
||||||
)
|
return nil, fmt.Errorf("can't connect to Github Copilot: %w", err)
|
||||||
}
|
}
|
||||||
defer client.Stop()
|
var err error
|
||||||
session, _ = client.CreateSession(context.Background(), &copilot.SessionConfig{
|
session, err = client.CreateSession(connectCtx, &copilot.SessionConfig{
|
||||||
Model: model,
|
Model: model,
|
||||||
Hooks: &copilot.SessionHooks{},
|
Hooks: &copilot.SessionHooks{},
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
client.Stop()
|
||||||
|
return nil, fmt.Errorf("failed to create Copilot session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &GitHubCopilotProvider{
|
return &GitHubCopilotProvider{
|
||||||
uri: uri,
|
uri: uri,
|
||||||
connectMode: connectMode,
|
connectMode: connectMode,
|
||||||
|
client: client,
|
||||||
session: session,
|
session: session,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *GitHubCopilotProvider) Close() {
|
||||||
|
if p.client != nil {
|
||||||
|
p.client.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Chat sends a chat request to GitHub Copilot
|
// Chat sends a chat request to GitHub Copilot
|
||||||
func (p *GitHubCopilotProvider) Chat(
|
func (p *GitHubCopilotProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) {
|
||||||
ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]any,
|
|
||||||
) (*LLMResponse, error) {
|
|
||||||
type tempMessage struct {
|
type tempMessage struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
|
@ -67,16 +81,25 @@ func (p *GitHubCopilotProvider) Chat(
|
||||||
|
|
||||||
fullcontent, _ := json.Marshal(out)
|
fullcontent, _ := json.Marshal(out)
|
||||||
|
|
||||||
content, _ := p.session.Send(ctx, copilot.MessageOptions{
|
event, err := p.session.SendAndWait(ctx, copilot.MessageOptions{
|
||||||
Prompt: string(fullcontent),
|
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{
|
return &LLMResponse{
|
||||||
FinishReason: "stop",
|
FinishReason: "stop",
|
||||||
Content: content,
|
Content: *event.Data.Content,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GitHubCopilotProvider) GetDefaultModel() string {
|
func (p *GitHubCopilotProvider) GetDefaultModel() string {
|
||||||
|
|
||||||
return "gpt-4.1"
|
return "gpt-4.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue