Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/picoclaw/cmd_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ func gatewayCmd() {
<-sigChan

fmt.Println("\nShutting down...")
if cp, ok := provider.(providers.StatefulProvider); ok {
cp.Close()
}
cancel()
healthServer.Stop(context.Background())
deviceService.Stop()
Expand Down
76 changes: 59 additions & 17 deletions pkg/providers/github_copilot_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,115 @@ import (
"context"
"encoding/json"
"fmt"
"sync"

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

mu sync.Mutex
}

func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*GitHubCopilotProvider, error) {
var session *copilot.Session
if connectMode == "" {
connectMode = "grpc"
}
switch connectMode {

switch connectMode {
case "stdio":
// todo
// TODO:
return nil, fmt.Errorf("stdio mode not implemented")
case "grpc":
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",
"can't connect to Github Copilot: %w; `https://github.com/github/copilot-sdk/blob/main/docs/getting-started.md#connecting-to-an-external-cli-server` for details",
err,
)
}
defer client.Stop()
session, _ = client.CreateSession(context.Background(), &copilot.SessionConfig{

session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
Model: model,
Hooks: &copilot.SessionHooks{},
})
if err != nil {

client.Stop()
return nil, fmt.Errorf("create session failed: %w", err)
}

return &GitHubCopilotProvider{
uri: uri,
connectMode: connectMode,
client: client,
session: session,
}, nil
default:
return nil, fmt.Errorf("unknown connect mode: %s", connectMode)
}
}

return &GitHubCopilotProvider{
uri: uri,
connectMode: connectMode,
session: session,
}, nil
func (p *GitHubCopilotProvider) Close() {
p.mu.Lock()
defer p.mu.Unlock()
Comment on lines +64 to +65

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it maybe be better if you lock only if the client is not nil?
Just to be sure to locking only if there's a real reason.

if p.client != nil {
p.client.Stop()
p.client = nil
p.session = nil
}
}

// 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,
ctx context.Context,
messages []Message,
tools []ToolDefinition,
model string,
options map[string]any,
) (*LLMResponse, error) {
type tempMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
out := make([]tempMessage, 0, len(messages))

for _, msg := range messages {
out = append(out, tempMessage{
Role: msg.Role,
Content: msg.Content,
})
}

fullcontent, _ := json.Marshal(out)
fullcontent, err := json.Marshal(out)
if err != nil {
return nil, fmt.Errorf("marshal messages: %w", err)
}
p.mu.Lock()
session := p.session
p.mu.Unlock()

if session == nil {
return nil, fmt.Errorf("provider closed")
}

content, _ := p.session.Send(ctx, copilot.MessageOptions{
resp, err := session.SendAndWait(ctx, copilot.MessageOptions{
Prompt: string(fullcontent),
})

if resp == nil {
return nil, fmt.Errorf("empty response from copilot")
}
if resp.Data.Content == nil {
return nil, fmt.Errorf("no content in copilot response")
}
content := *resp.Data.Content

return &LLMResponse{
FinishReason: "stop",
Content: content,
Expand Down
5 changes: 5 additions & 0 deletions pkg/providers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ type LLMProvider interface {
GetDefaultModel() string
}

type StatefulProvider interface {
LLMProvider
Close()
}

// FailoverReason classifies why an LLM request failed for fallback decisions.
type FailoverReason string

Expand Down