Files
awesome-copilot/instructions/copilot-sdk-go.instructions.md
2026-01-22 15:51:40 +11:00

16 KiB

applyTo, description, name
applyTo description name
**.go, go.mod This file provides guidance on building Go applications using GitHub Copilot SDK. GitHub Copilot SDK Go Instructions

Core Principles

  • The SDK is in technical preview and may have breaking changes
  • Requires Go 1.21 or later
  • Requires GitHub Copilot CLI installed and in PATH
  • Uses goroutines and channels for concurrent operations
  • No external dependencies beyond the standard library

Installation

Always install via Go modules:

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

Client Initialization

Basic Client Setup

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

client := copilot.NewClient(nil)
if err := client.Start(); err != nil {
    log.Fatal(err)
}
defer client.Stop()

Client Configuration Options

When creating a CopilotClient, use ClientOptions:

  • CLIPath - Path to CLI executable (default: "copilot" from PATH)
  • CLIUrl - URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a process
  • Port - Server port (default: 0 for random)
  • UseStdio - Use stdio transport instead of TCP (default: true)
  • LogLevel - Log level (default: "info")
  • AutoStart - Auto-start server (default: true, use pointer: boolPtr(true))
  • AutoRestart - Auto-restart on crash (default: true, use pointer: boolPtr(true))
  • Cwd - Working directory for the CLI process
  • Env - Environment variables for the CLI process ([]string)

Manual Server Control

For explicit control:

autoStart := false
client := copilot.NewClient(&copilot.ClientOptions{AutoStart: &autoStart})
if err := client.Start(); err != nil {
    log.Fatal(err)
}
// Use client...
client.Stop()

Use ForceStop() when Stop() takes too long.

Session Management

Creating Sessions

Use SessionConfig for configuration:

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    Streaming: true,
    Tools: []copilot.Tool{...},
    SystemMessage: &copilot.SystemMessageConfig{ ... },
    AvailableTools: []string{"tool1", "tool2"},
    ExcludedTools: []string{"tool3"},
    Provider: &copilot.ProviderConfig{ ... },
})
if err != nil {
    log.Fatal(err)
}

Session Config Options

  • SessionID - Custom session ID
  • Model - Model name ("gpt-5", "claude-sonnet-4.5", etc.)
  • Tools - Custom tools exposed to the CLI ([]Tool)
  • SystemMessage - System message customization (*SystemMessageConfig)
  • AvailableTools - Allowlist of tool names ([]string)
  • ExcludedTools - Blocklist of tool names ([]string)
  • Provider - Custom API provider configuration (BYOK) (*ProviderConfig)
  • Streaming - Enable streaming response chunks (bool)
  • MCPServers - MCP server configurations
  • CustomAgents - Custom agent configurations
  • ConfigDir - Config directory override
  • SkillDirectories - Skill directories ([]string)
  • DisabledSkills - Disabled skills ([]string)

Resuming Sessions

session, err := client.ResumeSession("session-id")
// Or with options:
session, err := client.ResumeSessionWithOptions("session-id", &copilot.ResumeSessionConfig{ ... })

Session Operations

  • session.SessionID - Get session identifier (string)
  • session.Send(copilot.MessageOptions{Prompt: "...", Attachments: []copilot.Attachment{...}}) - Send message, returns (messageID string, error)
  • session.SendAndWait(options, timeout) - Send and wait for idle, returns (*SessionEvent, error)
  • session.Abort() - Abort current processing, returns error
  • session.GetMessages() - Get all events/messages, returns ([]SessionEvent, error)
  • session.Destroy() - Clean up session, returns error

Event Handling

Event Subscription Pattern

ALWAYS use channels or done signals for waiting on session events:

done := make(chan struct{})

unsubscribe := session.On(func(evt copilot.SessionEvent) {
    switch evt.Type {
    case copilot.AssistantMessage:
        fmt.Println(*evt.Data.Content)
    case copilot.SessionIdle:
        close(done)
    }
})
defer unsubscribe()

session.Send(copilot.MessageOptions{Prompt: "..."})
<-done

Unsubscribing from Events

The On() method returns a function that unsubscribes:

unsubscribe := session.On(func(evt copilot.SessionEvent) {
    // handler
})
// Later...
unsubscribe()

Event Types

Use type switches for event handling:

session.On(func(evt copilot.SessionEvent) {
    switch evt.Type {
    case copilot.UserMessage:
        // Handle user message
    case copilot.AssistantMessage:
        if evt.Data.Content != nil {
            fmt.Println(*evt.Data.Content)
        }
    case copilot.ToolExecutionStart:
        // Tool execution started
    case copilot.ToolExecutionComplete:
        // Tool execution completed
    case copilot.SessionStart:
        // Session started
    case copilot.SessionIdle:
        // Session is idle (processing complete)
    case copilot.SessionError:
        if evt.Data.Message != nil {
            fmt.Println("Error:", *evt.Data.Message)
        }
    }
})

Streaming Responses

Enabling Streaming

Set Streaming: true in SessionConfig:

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

Handling Streaming Events

Handle both delta events (incremental) and final events:

done := make(chan struct{})

session.On(func(evt copilot.SessionEvent) {
    switch evt.Type {
    case copilot.AssistantMessageDelta:
        // Incremental text chunk
        if evt.Data.DeltaContent != nil {
            fmt.Print(*evt.Data.DeltaContent)
        }
    case copilot.AssistantReasoningDelta:
        // Incremental reasoning chunk (model-dependent)
        if evt.Data.DeltaContent != nil {
            fmt.Print(*evt.Data.DeltaContent)
        }
    case copilot.AssistantMessage:
        // Final complete message
        fmt.Println("\n--- Final ---")
        if evt.Data.Content != nil {
            fmt.Println(*evt.Data.Content)
        }
    case copilot.AssistantReasoning:
        // Final reasoning content
        fmt.Println("--- Reasoning ---")
        if evt.Data.Content != nil {
            fmt.Println(*evt.Data.Content)
        }
    case copilot.SessionIdle:
        close(done)
    }
})

session.Send(copilot.MessageOptions{Prompt: "Tell me a story"})
<-done

Note: Final events (AssistantMessage, AssistantReasoning) are ALWAYS sent regardless of streaming setting.

Custom Tools

Defining Tools

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    Tools: []copilot.Tool{
        {
            Name:        "lookup_issue",
            Description: "Fetch issue details from tracker",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "id": map[string]interface{}{
                        "type":        "string",
                        "description": "Issue ID",
                    },
                },
                "required": []string{"id"},
            },
            Handler: func(inv copilot.ToolInvocation) (copilot.ToolResult, error) {
                args := inv.Arguments.(map[string]interface{})
                issueID := args["id"].(string)

                issue, err := fetchIssue(issueID)
                if err != nil {
                    return copilot.ToolResult{}, err
                }

                return copilot.ToolResult{
                    TextResultForLLM: fmt.Sprintf("Issue: %v", issue),
                    ResultType:       "success",
                    ToolTelemetry:    map[string]interface{}{},
                }, nil
            },
        },
    },
})

Tool Return Types

  • Return ToolResult struct with fields:
    • TextResultForLLM (string) - Result text for the LLM
    • ResultType (string) - "success" or "failure"
    • Error (string, optional) - Internal error message (not shown to LLM)
    • ToolTelemetry (map[string]interface{}) - Telemetry data

Tool Execution Flow

When Copilot invokes a tool, the client automatically:

  1. Runs your handler function
  2. Returns the ToolResult
  3. Responds to the CLI

System Message Customization

Append Mode (Default - Preserves Guardrails)

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    SystemMessage: &copilot.SystemMessageConfig{
        Mode: "append",
        Content: `
<workflow_rules>
- Always check for security vulnerabilities
- Suggest performance improvements when applicable
</workflow_rules>
`,
    },
})

Replace Mode (Full Control - Removes Guardrails)

session, err := client.CreateSession(&copilot.SessionConfig{
    Model: "gpt-5",
    SystemMessage: &copilot.SystemMessageConfig{
        Mode:    "replace",
        Content: "You are a helpful assistant.",
    },
})

File Attachments

Attach files to messages using Attachment:

messageID, err := session.Send(copilot.MessageOptions{
    Prompt: "Analyze this file",
    Attachments: []copilot.Attachment{
        {
            Type:        "file",
            Path:        "/path/to/file.go",
            DisplayName: "My File",
        },
    },
})

Message Delivery Modes

Use the Mode field in MessageOptions:

  • "enqueue" - Queue message for processing
  • "immediate" - Process message immediately
session.Send(copilot.MessageOptions{
    Prompt: "...",
    Mode:   "enqueue",
})

Multiple Sessions

Sessions are independent and can run concurrently:

session1, _ := client.CreateSession(&copilot.SessionConfig{Model: "gpt-5"})
session2, _ := client.CreateSession(&copilot.SessionConfig{Model: "claude-sonnet-4.5"})

session1.Send(copilot.MessageOptions{Prompt: "Hello from session 1"})
session2.Send(copilot.MessageOptions{Prompt: "Hello from session 2"})

Bring Your Own Key (BYOK)

Use custom API providers via ProviderConfig:

session, err := client.CreateSession(&copilot.SessionConfig{
    Provider: &copilot.ProviderConfig{
        Type:    "openai",
        BaseURL: "https://api.openai.com/v1",
        APIKey:  "your-api-key",
    },
})

Session Lifecycle Management

Checking Connection State

state := client.GetState()
// Returns: "disconnected", "connecting", "connected", or "error"

Error Handling

Standard Exception Handling

session, err := client.CreateSession(&copilot.SessionConfig{})
if err != nil {
    log.Fatalf("Failed to create session: %v", err)
}

_, err = session.Send(copilot.MessageOptions{Prompt: "Hello"})
if err != nil {
    log.Printf("Failed to send: %v", err)
}

Session Error Events

Monitor SessionError type for runtime errors:

session.On(func(evt copilot.SessionEvent) {
    if evt.Type == copilot.SessionError {
        if evt.Data.Message != nil {
            fmt.Fprintf(os.Stderr, "Session Error: %s\n", *evt.Data.Message)
        }
    }
})

Connectivity Testing

Use Ping to verify server connectivity:

resp, err := client.Ping("test message")
if err != nil {
    log.Printf("Server unreachable: %v", err)
} else {
    log.Printf("Server responded at %d", resp.Timestamp)
}

Resource Cleanup

Cleanup with Defer

ALWAYS use defer for cleanup:

client := copilot.NewClient(nil)
if err := client.Start(); err != nil {
    log.Fatal(err)
}
defer client.Stop()

session, err := client.CreateSession(nil)
if err != nil {
    log.Fatal(err)
}
defer session.Destroy()

Manual Cleanup

If not using defer:

client := copilot.NewClient(nil)
err := client.Start()
if err != nil {
    log.Fatal(err)
}

session, err := client.CreateSession(nil)
if err != nil {
    client.Stop()
    log.Fatal(err)
}

// Use session...

session.Destroy()
errors := client.Stop()
for _, err := range errors {
    log.Printf("Cleanup error: %v", err)
}

Best Practices

  1. Always use defer for cleanup of clients and sessions
  2. Use channels to wait for SessionIdle event
  3. Handle SessionError events for robust error handling
  4. Use type switches for event handling
  5. Enable streaming for better UX in interactive scenarios
  6. Provide descriptive tool names and descriptions for better model understanding
  7. Call unsubscribe functions when no longer needed
  8. Use SystemMessageConfig with Mode: "append" to preserve safety guardrails
  9. Handle both delta and final events when streaming is enabled
  10. Check nil pointers in event data (Content, Message, etc. are pointers)

Common Patterns

Simple Query-Response

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"})
if err != nil {
    log.Fatal(err)
}
defer session.Destroy()

done := make(chan struct{})

session.On(func(evt copilot.SessionEvent) {
    if evt.Type == copilot.AssistantMessage && evt.Data.Content != nil {
        fmt.Println(*evt.Data.Content)
    } else if evt.Type == copilot.SessionIdle {
        close(done)
    }
})

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

Multi-Turn Conversation

session, _ := client.CreateSession(nil)
defer session.Destroy()

sendAndWait := func(prompt string) error {
    done := make(chan struct{})
    var eventErr error

    unsubscribe := session.On(func(evt copilot.SessionEvent) {
        switch evt.Type {
        case copilot.AssistantMessage:
            if evt.Data.Content != nil {
                fmt.Println(*evt.Data.Content)
            }
        case copilot.SessionIdle:
            close(done)
        case copilot.SessionError:
            if evt.Data.Message != nil {
                eventErr = fmt.Errorf(*evt.Data.Message)
            }
        }
    })
    defer unsubscribe()

    if _, err := session.Send(copilot.MessageOptions{Prompt: prompt}); err != nil {
        return err
    }
    <-done
    return eventErr
}

sendAndWait("What is the capital of France?")
sendAndWait("What is its population?")

SendAndWait Helper

// Use built-in SendAndWait for simpler synchronous interaction
response, err := session.SendAndWait(copilot.MessageOptions{
    Prompt: "What is 2+2?",
}, 0) // 0 uses default 60s timeout

if err != nil {
    log.Printf("Error: %v", err)
}
if response != nil && response.Data.Content != nil {
    fmt.Println(*response.Data.Content)
}

Tool with Struct Return Type

type UserInfo struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
    Role  string `json:"role"`
}

session, _ := client.CreateSession(&copilot.SessionConfig{
    Tools: []copilot.Tool{
        {
            Name:        "get_user",
            Description: "Retrieve user information",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "user_id": map[string]interface{}{
                        "type":        "string",
                        "description": "User ID",
                    },
                },
                "required": []string{"user_id"},
            },
            Handler: func(inv copilot.ToolInvocation) (copilot.ToolResult, error) {
                args := inv.Arguments.(map[string]interface{})
                userID := args["user_id"].(string)

                user := UserInfo{
                    ID:    userID,
                    Name:  "John Doe",
                    Email: "john@example.com",
                    Role:  "Developer",
                }

                jsonBytes, _ := json.Marshal(user)
                return copilot.ToolResult{
                    TextResultForLLM: string(jsonBytes),
                    ResultType:       "success",
                    ToolTelemetry:    map[string]interface{}{},
                }, nil
            },
        },
    },
})