A Go SDK for programmatic access to the GitHub Copilot CLI.
Note: This SDK is in technical preview and may change in breaking ways.
go get github.com/github/copilot-sdk/gopackage main
import (
"fmt"
"log"
copilot "github.com/github/copilot-sdk/go"
)
func main() {
// Create client
client := copilot.NewClient(&copilot.ClientOptions{
LogLevel: "error",
})
// Start the client
if err := client.Start(); err != nil {
log.Fatal(err)
}
defer client.Stop()
// Create a session
session, err := client.CreateSession(&copilot.SessionConfig{
Model: "gpt-5",
})
if err != nil {
log.Fatal(err)
}
defer session.Destroy()
// Set up event handler
done := make(chan bool)
session.On(func(event copilot.SessionEvent) {
if event.Type == "assistant.message" {
if event.Data.Content != nil {
fmt.Println(*event.Data.Content)
}
}
if event.Type == "session.idle" {
close(done)
}
})
// Send a message
_, err = session.Send(copilot.MessageOptions{
Prompt: "What is 2+2?",
})
if err != nil {
log.Fatal(err)
}
// Wait for completion
<-done
}NewClient(options *ClientOptions) *Client- Create a new clientStart() error- Start the CLI serverStop() []error- Stop the CLI server (returns array of errors, empty if all succeeded)ForceStop()- Forcefully stop without graceful cleanupCreateSession(config *SessionConfig) (*Session, error)- Create a new sessionResumeSession(sessionID string) (*Session, error)- Resume an existing sessionResumeSessionWithOptions(sessionID string, config *ResumeSessionConfig) (*Session, error)- Resume with additional configurationGetState() ConnectionState- Get connection statePing(message string) (*PingResponse, error)- Ping the server
ClientOptions:
CLIPath(string): Path to CLI executable (default: "copilot" orCOPILOT_CLI_PATHenv var)CLIUrl(string): URL of existing CLI server (e.g.,"localhost:8080","http://127.0.0.1:9000", or just"8080"). When provided, the client will not spawn a CLI process.Cwd(string): Working directory for CLI processPort(int): Server port for TCP mode (default: 0 for random)UseStdio(bool): Use stdio transport instead of TCP (default: true)LogLevel(string): Log level (default: "info")AutoStart(*bool): Auto-start server on first use (default: true). UseBool(false)to disable.AutoRestart(*bool): Auto-restart on crash (default: true). UseBool(false)to disable.Env([]string): Environment variables for CLI process (default: inherits from current process)
ResumeSessionConfig:
Tools([]Tool): Tools to expose when resumingProvider(*ProviderConfig): Custom model provider configuration
Send(options MessageOptions) (string, error)- Send a messageOn(handler SessionEventHandler) func()- Subscribe to events (returns unsubscribe function)Abort() error- Abort the currently processing messageGetMessages() ([]SessionEvent, error)- Get message historyDestroy() error- Destroy the session
Bool(v bool) *bool- Helper to create bool pointers forAutoStart/AutoRestartoptions
Expose your own functionality to Copilot by attaching tools to a session.
Use DefineTool for type-safe tools with automatic JSON schema generation:
type LookupIssueParams struct {
ID string `json:"id" jsonschema:"Issue identifier"`
}
lookupIssue := copilot.DefineTool("lookup_issue", "Fetch issue details from our tracker",
func(params LookupIssueParams, inv copilot.ToolInvocation) (any, error) {
// params is automatically unmarshaled from the LLM's arguments
issue, err := fetchIssue(params.ID)
if err != nil {
return nil, err
}
return issue.Summary, nil
})
session, _ := client.CreateSession(&copilot.SessionConfig{
Model: "gpt-5",
Tools: []copilot.Tool{lookupIssue},
})For more control over the JSON schema, use the Tool struct directly:
lookupIssue := copilot.Tool{
Name: "lookup_issue",
Description: "Fetch issue details from our tracker",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "string",
"description": "Issue identifier",
},
},
"required": []string{"id"},
},
Handler: func(invocation copilot.ToolInvocation) (copilot.ToolResult, error) {
args := invocation.Arguments.(map[string]interface{})
issue, err := fetchIssue(args["id"].(string))
if err != nil {
return copilot.ToolResult{}, err
}
return copilot.ToolResult{
TextResultForLLM: issue.Summary,
ResultType: "success",
SessionLog: fmt.Sprintf("Fetched issue %s", issue.ID),
}, nil
},
}
session, _ := client.CreateSession(&copilot.SessionConfig{
Model: "gpt-5",
Tools: []copilot.Tool{lookupIssue},
})When the model selects a tool, the SDK automatically runs your handler (in parallel with other calls) and responds to the CLI's tool.call with the handler's result.
Enable streaming to receive assistant response chunks as they're generated:
package main
import (
"fmt"
"log"
copilot "github.com/github/copilot-sdk/go"
)
func main() {
client := copilot.NewClient(nil)
if err := client.Start(); err != nil {
log.Fatal(err)
}
defer client.Stop()
session, err := client.CreateSession(&copilot.SessionConfig{
Model: "gpt-5",
Streaming: true,
})
if err != nil {
log.Fatal(err)
}
defer session.Destroy()
done := make(chan bool)
session.On(func(event copilot.SessionEvent) {
if event.Type == "assistant.message_delta" {
// Streaming message chunk - print incrementally
if event.Data.DeltaContent != nil {
fmt.Print(*event.Data.DeltaContent)
}
} else if event.Type == "assistant.reasoning_delta" {
// Streaming reasoning chunk (if model supports reasoning)
if event.Data.DeltaContent != nil {
fmt.Print(*event.Data.DeltaContent)
}
} else if event.Type == "assistant.message" {
// Final message - complete content
fmt.Println("\n--- Final message ---")
if event.Data.Content != nil {
fmt.Println(*event.Data.Content)
}
} else if event.Type == "assistant.reasoning" {
// Final reasoning content (if model supports reasoning)
fmt.Println("--- Reasoning ---")
if event.Data.Content != nil {
fmt.Println(*event.Data.Content)
}
}
if event.Type == "session.idle" {
close(done)
}
})
_, err = session.Send(copilot.MessageOptions{
Prompt: "Tell me a short story",
})
if err != nil {
log.Fatal(err)
}
<-done
}When Streaming: true:
assistant.message_deltaevents are sent withDeltaContentcontaining incremental textassistant.reasoning_deltaevents are sent withDeltaContentfor reasoning/chain-of-thought (model-dependent)- Accumulate
DeltaContentvalues to build the full response progressively - The final
assistant.messageandassistant.reasoningevents contain the complete content
Note: assistant.message and assistant.reasoning (final events) are always sent regardless of streaming setting.
Communicates with CLI via stdin/stdout pipes. Recommended for most use cases.
client := copilot.NewClient(nil) // Uses stdio by defaultCommunicates with CLI via TCP socket. Useful for distributed scenarios.
COPILOT_CLI_PATH- Path to the Copilot CLI executable
MIT