diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 191ff8239d..e424fb7d35 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -9,6 +9,7 @@ package main import ( "bufio" "context" + "embed" "fmt" "io" "io/fs" @@ -18,7 +19,6 @@ import ( "runtime" "strings" "time" - "embed" "github.com/chzyer/readline" "github.com/sipeed/picoclaw/pkg/agent" @@ -41,7 +41,6 @@ import ( //go:embed workspace var embeddedFiles embed.FS - var ( version = "dev" gitCommit string @@ -214,7 +213,6 @@ func printHelp() { fmt.Println(" version Show version information") } - func onboard() { configPath := getConfigPath() @@ -246,50 +244,50 @@ func onboard() { } func copyEmbeddedToTarget(targetDir string) error { - // Ensure target directory exists - if err := os.MkdirAll(targetDir, 0755); err != nil { - return fmt.Errorf("Failed to create target directory: %w", err) - } - - // Walk through all files in embed.FS - err := fs.WalkDir(embeddedFiles, "workspace", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - // Skip directories - if d.IsDir() { - return nil - } - - // Read embedded file - data, err := embeddedFiles.ReadFile(path) - if err != nil { - return fmt.Errorf("Failed to read embedded file %s: %w", path, err) - } + // Ensure target directory exists + if err := os.MkdirAll(targetDir, 0755); err != nil { + return fmt.Errorf("Failed to create target directory: %w", err) + } + + // Walk through all files in embed.FS + err := fs.WalkDir(embeddedFiles, "workspace", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + // Skip directories + if d.IsDir() { + return nil + } + + // Read embedded file + data, err := embeddedFiles.ReadFile(path) + if err != nil { + return fmt.Errorf("Failed to read embedded file %s: %w", path, err) + } new_path, err := filepath.Rel("workspace", path) if err != nil { return fmt.Errorf("Failed to get relative path for %s: %v\n", path, err) } - // Build target file path - targetPath := filepath.Join(targetDir, new_path) + // Build target file path + targetPath := filepath.Join(targetDir, new_path) - // Ensure target file's directory exists - if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { - return fmt.Errorf("Failed to create directory %s: %w", filepath.Dir(targetPath), err) - } + // Ensure target file's directory exists + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + return fmt.Errorf("Failed to create directory %s: %w", filepath.Dir(targetPath), err) + } - // Write file - if err := os.WriteFile(targetPath, data, 0644); err != nil { - return fmt.Errorf("Failed to write file %s: %w", targetPath, err) - } + // Write file + if err := os.WriteFile(targetPath, data, 0644); err != nil { + return fmt.Errorf("Failed to write file %s: %w", targetPath, err) + } - return nil - }) + return nil + }) - return err + return err } func createWorkspaceTemplates(workspace string) { diff --git a/go.mod b/go.mod index f4c233ea85..206f1aafd0 100644 --- a/go.mod +++ b/go.mod @@ -19,12 +19,16 @@ require ( golang.org/x/oauth2 v0.35.0 ) + + require ( github.com/andybalholm/brotli v1.2.0 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/github/copilot-sdk/go v0.1.23 + github.com/google/jsonschema-go v0.4.2 // indirect github.com/go-resty/resty/v2 v2.17.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/grbit/go-json v0.11.0 // indirect @@ -43,4 +47,5 @@ require ( golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect + ) diff --git a/go.sum b/go.sum index 9174d28892..8f95751226 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/github/copilot-sdk/go v0.1.23 h1:uExtO/inZQndCZMiSAA1hvXINiz9tqo/MZgQzFzurxw= +github.com/github/copilot-sdk/go v0.1.23/go.mod h1:GdwwBfMbm9AABLEM3x5IZKw4ZfwCYxZ1BgyytmZenQ0= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= @@ -56,6 +58,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/pkg/config/config.go b/pkg/config/config.go index bbfa2e40b0..b6d4e0b7f5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -157,24 +157,26 @@ type DevicesConfig struct { } type ProvidersConfig struct { - Anthropic ProviderConfig `json:"anthropic"` - OpenAI ProviderConfig `json:"openai"` - OpenRouter ProviderConfig `json:"openrouter"` - Groq ProviderConfig `json:"groq"` - Zhipu ProviderConfig `json:"zhipu"` - VLLM ProviderConfig `json:"vllm"` - Gemini ProviderConfig `json:"gemini"` - Nvidia ProviderConfig `json:"nvidia"` - Moonshot ProviderConfig `json:"moonshot"` - ShengSuanYun ProviderConfig `json:"shengsuanyun"` - DeepSeek ProviderConfig `json:"deepseek"` + Anthropic ProviderConfig `json:"anthropic"` + OpenAI ProviderConfig `json:"openai"` + OpenRouter ProviderConfig `json:"openrouter"` + Groq ProviderConfig `json:"groq"` + Zhipu ProviderConfig `json:"zhipu"` + VLLM ProviderConfig `json:"vllm"` + Gemini ProviderConfig `json:"gemini"` + Nvidia ProviderConfig `json:"nvidia"` + Moonshot ProviderConfig `json:"moonshot"` + ShengSuanYun ProviderConfig `json:"shengsuanyun"` + DeepSeek ProviderConfig `json:"deepseek"` + GitHubCopilot ProviderConfig `json:"github_copilot"` } type ProviderConfig struct { - APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"` - APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"` - Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"` - AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"` + APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"` + APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"` + Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"` + AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"` + ConnectMode string `json:"connect_mode,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_CONNECT_MODE"` //only for Github Copilot, `stdio` or `grpc` } type GatewayConfig struct { diff --git a/pkg/providers/github_copilot_provider.go b/pkg/providers/github_copilot_provider.go new file mode 100644 index 0000000000..5058819f52 --- /dev/null +++ b/pkg/providers/github_copilot_provider.go @@ -0,0 +1,82 @@ +package providers + +import ( + "context" + "fmt" + + json "encoding/json" + + copilot "github.com/github/copilot-sdk/go" +) + +type GitHubCopilotProvider struct { + uri string + connectMode string // `stdio` or `grpc`` + + session *copilot.Session +} + +func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*GitHubCopilotProvider, error) { + + var session *copilot.Session + if connectMode == "" { + connectMode = "grpc" + } + switch connectMode { + + case "stdio": + //todo + 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") + } + defer client.Stop() + session, _ = client.CreateSession(context.Background(), &copilot.SessionConfig{ + Model: model, + Hooks: &copilot.SessionHooks{}, + }) + + } + + return &GitHubCopilotProvider{ + uri: uri, + connectMode: connectMode, + session: 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]interface{}) (*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) + + content, _ := p.session.Send(ctx, copilot.MessageOptions{ + Prompt: string(fullcontent), + }) + + return &LLMResponse{ + FinishReason: "stop", + Content: content, + }, nil + +} + +func (p *GitHubCopilotProvider) GetDefaultModel() string { + + return "gpt-4.1" +} diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 6fcbd30556..0bea16d6a8 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -314,7 +314,16 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { model = "deepseek-chat" } } + case "github_copilot", "copilot": + if cfg.Providers.GitHubCopilot.APIBase != "" { + apiBase = cfg.Providers.GitHubCopilot.APIBase + } else { + apiBase = "localhost:4321" + } + return NewGitHubCopilotProvider(apiBase, cfg.Providers.GitHubCopilot.ConnectMode, model) + } + } // Fallback: detect provider from model name