Awesome-omni-skill copilot-sdk-go

Expert guidance for using the GitHub Copilot CLI SDK with Go, including API reference, best practices, and common usage patterns.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/copilot-sdk-go" ~/.claude/skills/diegosouzapw-awesome-omni-skill-copilot-sdk-go && rm -rf "$T"
manifest: skills/development/copilot-sdk-go/SKILL.md
source content

GitHub Copilot SDK for Go - Skill Guide

This skill provides comprehensive guidance for using the GitHub Copilot CLI SDK with Go programming language.

Reference Documentation Locations

When working with the Copilot SDK for Go, always refer to these documentation sources:

Core SDK Documentation

Cookbook & Examples

Go Recipes (with runnable examples)

Test Examples

Installation

go get github.com/github/copilot-sdk/go

Core Concepts

1. Client Creation and Lifecycle

import copilot "github.com/github/copilot-sdk/go"

// Create client with default options
client := copilot.NewClient(nil)

// Or with custom options
client := copilot.NewClient(&copilot.ClientOptions{
    LogLevel:    "error",
    UseStdio:    true,
    AutoStart:   copilot.Bool(true),
    AutoRestart: copilot.Bool(true),
})

// Always start the client
if err := client.Start(); err != nil {
    log.Fatal(err)
}
defer client.Stop()

Client Options:

  • CLIPath
    : Path to CLI executable (default: "copilot")
  • CLIUrl
    : Connect to existing server (format: "localhost:8080" or just "8080")
  • UseStdio
    : Use stdio transport (default: true)
  • Port
    : Server port for TCP mode
  • LogLevel
    : Log level (default: "info")
  • AutoStart
    : Auto-start server (default: true, use
    Bool(false)
    to disable)
  • AutoRestart
    : Auto-restart on crash (default: true)
  • GithubToken
    : GitHub token for auth
  • UseLoggedInUser
    : Use logged-in user auth (default: true)

2. Session Management

// Create a new session
session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",  // Required when using custom provider
    SessionID: "my-custom-id",  // Optional custom ID
    Streaming: true,  // Enable streaming responses
})
if err != nil {
    log.Fatal(err)
}
defer session.Destroy()

// Resume an existing session
session, err := client.ResumeSession("session-id")

// List all sessions
sessions, err := client.ListSessions()

// Delete a session
err := client.DeleteSession("session-id")

SessionConfig Options:

  • Model
    : Model to use ("gpt-5", "claude-sonnet-4.5", etc.) - Required with custom provider
  • SessionID
    : Custom session ID
  • Tools
    : Custom tools ([]Tool)
  • SystemMessage
    : System message config
  • Provider
    : Custom API provider (BYOK)
  • Streaming
    : Enable streaming deltas
  • InfiniteSessions
    : Auto-compaction config
  • OnUserInputRequest
    : Handler for ask_user tool
  • Hooks
    : Session lifecycle hooks
  • MCPServers
    : MCP server configurations
  • CustomAgents
    : Custom agent configs

3. Sending Messages and Handling Events

// Set up event handler
done := make(chan bool)
session.On(func(event copilot.SessionEvent) {
    switch event.Type {
    case "assistant.message":
        if event.Data.Content != nil {
            fmt.Println(*event.Data.Content)
        }
    case "assistant.message_delta":
        // Streaming chunk
        if event.Data.DeltaContent != nil {
            fmt.Print(*event.Data.DeltaContent)
        }
    case "session.idle":
        close(done)
    }
})

// Send a message
_, err = session.Send(copilot.MessageOptions{
    Prompt: "What is 2+2?",
    Attachments: []copilot.Attachment{
        {Type: "file", Path: "/path/to/file.txt"},
    },
})

<-done  // Wait for completion

4. Custom Tools

Using

DefineTool
(recommended - type-safe with auto schema):

type LookupParams struct {
    ID string `json:"id" jsonschema:"Issue identifier"`
}

lookupTool := copilot.DefineTool("lookup_issue",
    "Fetch issue details",
    func(params LookupParams, inv copilot.ToolInvocation) (any, error) {
        // params is automatically unmarshaled
        return fetchIssue(params.ID)
    })

session, _ := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    Tools: []copilot.Tool{lookupTool},
})

Using Tool struct directly (more control):

tool := copilot.Tool{
    Name:        "my_tool",
    Description: "Tool description",
    Parameters: map[string]interface{}{
        "type": "object",
        "properties": map[string]interface{}{
            "param": map[string]interface{}{
                "type": "string",
                "description": "Parameter description",
            },
        },
        "required": []string{"param"},
    },
    Handler: func(inv copilot.ToolInvocation) (copilot.ToolResult, error) {
        args := inv.Arguments.(map[string]interface{})
        // Process args
        return copilot.ToolResult{
            TextResultForLLM: "result",
            ResultType:       "success",
        }, nil
    },
}

5. Custom Providers (BYOK)

Ollama (local):

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "deepseek-coder-v2:16b",  // Required!
    Provider: &copilot.ProviderConfig{
        Type:    "openai",
        BaseURL: "http://localhost:11434/v1",
        // APIKey not needed for Ollama
    },
})

Azure OpenAI:

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-4",
    Provider: &copilot.ProviderConfig{
        Type:    "azure",  // Must be "azure", not "openai"!
        BaseURL: "https://my-resource.openai.azure.com",
        APIKey:  os.Getenv("AZURE_OPENAI_KEY"),
        Azure: &copilot.AzureProviderOptions{
            APIVersion: "2024-10-21",
        },
    },
})

Custom OpenAI-compatible:

Provider: &copilot.ProviderConfig{
    Type:    "openai",
    BaseURL: "https://my-api.example.com/v1",
    APIKey:  os.Getenv("MY_API_KEY"),
}

6. Streaming Responses

session, err := client.CreateSession(&copilot.SessionConfig{
    Model:     "gpt-5",
    Streaming: true,  // Enable streaming
})

session.On(func(event copilot.SessionEvent) {
    if event.Type == "assistant.message_delta" {
        // Incremental chunks
        if event.Data.DeltaContent != nil {
            fmt.Print(*event.Data.DeltaContent)
        }
    } else if event.Type == "assistant.message" {
        // Final complete message
        fmt.Println("\nComplete!")
    }
})

7. Infinite Sessions (Auto-compaction)

// Default: enabled with default thresholds
session, _ := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
})

// Custom thresholds
session, _ := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    InfiniteSessions: &copilot.InfiniteSessionConfig{
        Enabled:                       copilot.Bool(true),
        BackgroundCompactionThreshold: copilot.Float64(0.80),  // 80%
        BufferExhaustionThreshold:     copilot.Float64(0.95),  // 95%
    },
})

// Disable
InfiniteSessions: &copilot.InfiniteSessionConfig{
    Enabled: copilot.Bool(false),
}

// Access workspace path
fmt.Println(session.WorkspacePath())
// => ~/.copilot/session-state/{sessionId}/

8. User Input Requests (ask_user tool)

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    OnUserInputRequest: func(req copilot.UserInputRequest, inv copilot.UserInputInvocation) (copilot.UserInputResponse, error) {
        fmt.Printf("Question: %s\n", req.Question)
        if len(req.Choices) > 0 {
            fmt.Printf("Choices: %v\n", req.Choices)
        }

        // Get user input (implement your UI logic here)
        answer := getUserInput()

        return copilot.UserInputResponse{
            Answer:      answer,
            WasFreeform: len(req.Choices) == 0,
        }, nil
    },
})

9. Session Hooks

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    Hooks: &copilot.SessionHooks{
        OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {
            fmt.Printf("About to run: %s\n", input.ToolName)
            return &copilot.PreToolUseHookOutput{
                PermissionDecision: "allow",  // "allow", "deny", "ask"
                ModifiedArgs:       input.ToolArgs,
                AdditionalContext:  "Extra context",
            }, nil
        },
        OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {
            fmt.Printf("Tool completed: %s\n", input.ToolName)
            return &copilot.PostToolUseHookOutput{
                AdditionalContext: "Post-execution notes",
            }, nil
        },
        OnSessionStart: func(input copilot.SessionStartHookInput, inv copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) {
            fmt.Printf("Session started: %s\n", input.Source)  // "startup", "resume", "new"
            return nil, nil
        },
        OnSessionEnd: func(input copilot.SessionEndHookInput, inv copilot.HookInvocation) (*copilot.SessionEndHookOutput, error) {
            fmt.Printf("Session ended: %s\n", input.Reason)
            return nil, nil
        },
        OnErrorOccurred: func(input copilot.ErrorOccurredHookInput, inv copilot.HookInvocation) (*copilot.ErrorOccurredHookOutput, error) {
            return &copilot.ErrorOccurredHookOutput{
                ErrorHandling: "retry",  // "retry", "skip", "abort"
            }, nil
        },
    },
})

Best Practices

Error Handling

  1. Always use
    defer client.Stop()
    and
    defer session.Destroy()
  2. Handle connection errors (CLI might not be installed)
  3. Set appropriate timeouts for long-running requests
  4. Use context for cancellation
  5. Wrap errors with
    fmt.Errorf
    and
    %w
    for error chains

Example:

func doWork() error {
    client := copilot.NewClient(nil)
    if err := client.Start(); err != nil {
        return fmt.Errorf("failed to start: %w", err)
    }
    defer client.Stop()

    session, err := client.CreateSession(&copilot.SessionConfig{Model: "gpt-5"})
    if err != nil {
        return fmt.Errorf("failed to create session: %w", err)
    }
    defer session.Destroy()

    // Work with session...
    return nil
}

Multiple Sessions

  • One session per user in multi-user apps
  • Separate sessions for different tasks
  • Use custom session IDs for easy tracking
  • Clean up sessions with
    DeleteSession()
    when done

Streaming

  • Enable
    Streaming: true
    for real-time responses
  • Handle
    assistant.message_delta
    for incremental updates
  • Handle
    assistant.reasoning_delta
    for model reasoning (if supported)
  • Final
    assistant.message
    always contains complete content

Image Support

_, err = session.Send(copilot.MessageOptions{
    Prompt: "What's in this image?",
    Attachments: []copilot.Attachment{
        {Type: "file", Path: "/path/to/image.jpg"},
    },
})

Common Patterns

Pattern 1: Simple Question-Answer

client := copilot.NewClient(nil)
client.Start()
defer client.Stop()

session, _ := client.CreateSession(&copilot.SessionConfig{Model: "gpt-5"})
defer session.Destroy()

done := make(chan string)
session.On(func(event copilot.SessionEvent) {
    if event.Type == "assistant.message" && event.Data.Content != nil {
        done <- *event.Data.Content
    }
})

session.Send(copilot.MessageOptions{Prompt: "What is 2+2?"})
fmt.Println(<-done)

Pattern 2: Multiple Sessions

session1, _ := client.CreateSession(&copilot.SessionConfig{
    SessionID: "python-help",
    Model:     "gpt-5",
})
session2, _ := client.CreateSession(&copilot.SessionConfig{
    SessionID: "go-help",
    Model:     "gpt-5",
})

session1.Send(copilot.MessageOptions{Prompt: "Python question"})
session2.Send(copilot.MessageOptions{Prompt: "Go question"})

Pattern 3: Custom Tool with Type Safety

type GitStatusParams struct {
    Repo string `json:"repo" jsonschema:"Repository path"`
}

gitStatus := copilot.DefineTool("git_status", "Get git status",
    func(params GitStatusParams, inv copilot.ToolInvocation) (any, error) {
        output, err := exec.Command("git", "-C", params.Repo, "status").Output()
        return string(output), err
    })

Pattern 4: Graceful Shutdown

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)

go func() {
    <-sigChan
    fmt.Println("\nShutting down...")
    client.Stop()
    os.Exit(0)
}()

Helper Functions

  • Bool(v bool) *bool
    - Create bool pointers for options
  • Float64(v float64) *float64
    - Create float64 pointers for thresholds
AutoStart: copilot.Bool(false)
BackgroundCompactionThreshold: copilot.Float64(0.80)

Transport Modes

  • stdio (default): Stdin/stdout pipes - recommended for most cases
  • TCP: Socket communication - useful for distributed scenarios
// stdio (default)
client := copilot.NewClient(nil)

// TCP with custom port
client := copilot.NewClient(&copilot.ClientOptions{
    UseStdio: false,
    Port:     8080,
})

// Connect to existing server
client := copilot.NewClient(&copilot.ClientOptions{
    CLIUrl: "localhost:8080",
})

Environment Variables

  • COPILOT_CLI_PATH
    : Path to Copilot CLI executable

Troubleshooting

CLI not found

if err := client.Start(); err != nil {
    var execErr *exec.Error
    if errors.As(err, &execErr) {
        log.Fatal("Copilot CLI not found. Please install it.")
    }
}

Connection timeout

Use context with timeout when sending messages.

Custom provider not working

  • Ensure
    Model
    is specified (required with custom providers)
  • For Azure, use
    Type: "azure"
    , not
    "openai"
  • BaseURL should be just the host, no
    /v1
    path for Azure

When to Use What

Use CaseSolution
Simple chatBasic session with default config
Real-time responses
Streaming: true
Long conversationsInfinite sessions (default)
Custom functionalityDefine tools with
DefineTool
Local modelsCustom provider with Ollama
Multi-user appMultiple sessions with custom IDs
Permission controlSession hooks with
OnPreToolUse
User interaction
OnUserInputRequest
handler
Different modelsMultiple sessions with different configs

Additional Resources

  • Full E2E test examples:
    ./reference/go/e2e/
  • All type definitions:
    ./reference/go/types.go
  • Client internals:
    ./reference/go/client.go
  • Session internals:
    ./reference/go/session.go

Quick Reference

// Essential imports
import copilot "github.com/github/copilot-sdk/go"

// Client lifecycle
client := copilot.NewClient(nil)
client.Start()
defer client.Stop()

// Session lifecycle
session, _ := client.CreateSession(&copilot.SessionConfig{Model: "gpt-5"})
defer session.Destroy()

// Event handling
session.On(func(event copilot.SessionEvent) { /* ... */ })

// Send message
session.Send(copilot.MessageOptions{Prompt: "Hello"})

// Custom tool
copilot.DefineTool("name", "desc", func(params Type, inv copilot.ToolInvocation) (any, error) { /* ... */ })