How to Use the GitHub Copilot SDK with Go: Implementation Guide and Examples

The GitHub Copilot SDK for Go enables applications to communicate with the Copilot CLI over JSON-RPC, providing session management, tool integration, and real-time streaming through the Client and Session types in the github.com/github/copilot-sdk/go package.

The GitHub Copilot SDK for Go allows developers to programmatically interact with GitHub Copilot's LLM capabilities from within Go applications. By establishing a JSON-RPC connection to the Copilot CLI, the SDK provides a type-safe interface for creating conversational sessions, registering custom tools, and handling permission requests. This implementation guide covers the complete architecture and usage patterns based on the actual source code in the github/copilot-sdk repository.

Architecture Overview

The SDK follows a layered architecture that separates connection management from conversation state. Understanding these components is essential for building robust integrations.

Client (go/client.go)

The Client manages the connection to the Copilot CLI runtime. According to the source at [go/client.go](https://github.com/github/copilot-sdk/blob/main/go/client.go), the NewClient function (lines 55-66) initializes this connection, supporting stdio transport with an embedded CLI binary, or remote TCP/URI endpoints. The client handles the low-level JSON-RPC transport via go/internal/jsonrpc2/jsonrpc2.go.

Session (go/session.go)

A Session represents a single conversation with the assistant. Implemented in [go/session.go](https://github.com/github/copilot-sdk/blob/main/go/session.go), the session holds a unique ID, workspace state for checkpointing, and event handlers. The CreateSession method in client.go (lines 92-98) instantiates sessions using a SessionConfig that specifies models, tools, and permission handlers.

Message Flow and Events

Messages flow through Session.Send or SendAndWait, which post prompts with optional attachments. The CLI returns a messageId and streams events including assistant.message, assistant.message_delta, session.idle, and tool requests. These events are delivered to handlers registered via Session.On (lines 64-73 of session.go).

Tool Integration

Custom functions are registered as tools using helpers in [go/definetool.go](https://github.com/github/copilot-sdk/blob/main/go/definetool.go). The SDK automatically marshals LLM arguments into Go structs and returns results to the CLI. The execution path runs through executeToolAndRespond in session.go (lines 321-340).

Permission and UI Handling

Before executing actions like file writes or shell commands, the SDK invokes the OnPermissionRequest handler configured in SessionConfig. Use copilot.PermissionHandler.ApproveAll for automated workflows, or return specific rpc.PermissionDecision* variants for manual approval. For user interaction, implement OnUserInputRequest to handle elicitation requests.

Installation and Setup

Install the SDK using Go modules:

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

To embed the Copilot CLI binary directly into your Go application, install the bundler tool:

go get -tool github.com/github/copilot-sdk/go/cmd/bundler
go tool bundler

This extracts the CLI at runtime via go/internal/embeddedcli/embeddedcli.go, eliminating external dependencies.

Basic Hello World Implementation

This example demonstrates the minimal setup required to send a prompt and receive a response:

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	// Initialize client with default stdio transport
	client := copilot.NewClient(nil)
	
	// Start the runtime
	if err := client.Start(context.Background()); err != nil {
		log.Fatal(err)
	}
	defer client.Stop()

	// Create session with permission handler
	sess, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
		Model:               "gpt-4",
		OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer sess.Disconnect()

	// Subscribe to assistant messages
	unsub := sess.On(func(ev copilot.SessionEvent) {
		if d, ok := ev.Data.(*copilot.AssistantMessageData); ok {
			fmt.Println("Assistant:", d.Content)
		}
	})
	defer unsub()

	// Send prompt
	if _, err = sess.Send(context.Background(),
		copilot.MessageOptions{Prompt: "What is 2 + 2?"}); err != nil {
		log.Fatal(err)
	}
}

The NewClient(nil) call uses the embedded CLI when ClientOptions.Connection is empty, as implemented in the Client struct initialization.

Advanced Features

Registering Type-Safe Tools

Define custom tools using structured Go types for automatic JSON schema generation:

package main

import (
	"context"
	"fmt"
	"log"

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

type WeatherParams struct {
	City string `json:"city" jsonschema:"The city name to look up weather for"`
}

var weatherTool = copilot.DefineTool(
	"get_weather",
	"Fetch the current weather for a city",
	func(p WeatherParams, inv copilot.ToolInvocation) (any, error) {
		return map[string]string{
			"city":   p.City,
			"status": "Sunny",
			"temp":   "23°C",
		}, nil
	})

func main() {
	client := copilot.NewClient(nil)
	client.Start(context.Background())
	defer client.Stop()

	s, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
		Model:               "gpt-4",
		Tools:               []copilot.Tool{weatherTool},
		OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer s.Disconnect()

	unsub := s.On(func(ev copilot.SessionEvent) {
		if msg, ok := ev.Data.(*copilot.AssistantMessageData); ok {
			fmt.Println("Assistant:", msg.Content)
		}
	})
	defer unsub()

	s.Send(context.Background(),
		copilot.MessageOptions{Prompt: "What's the weather in Paris?"})
}

The DefineTool helper in definetool.go uses reflection to generate JSON schemas from struct tags, enabling type-safe argument marshaling.

Streaming Assistant Responses

Enable real-time token streaming to display responses as they are generated:

func main() {
	client := copilot.NewClient(nil)
	client.Start(context.Background())
	defer client.Stop()

	s, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
		Model:               "gpt-4",
		Streaming:           copilot.Bool(true), // Enable streaming
		OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer s.Disconnect()

	s.On(func(ev copilot.SessionEvent) {
		switch d := ev.Data.(type) {
		case *copilot.AssistantMessageDeltaData:
			fmt.Print(d.DeltaContent) // Incremental tokens
		case *copilot.AssistantMessageData:
			fmt.Println("\n--- Full message completed ---")
			fmt.Println(d.Content)
		}
	})

	s.Send(context.Background(),
		copilot.MessageOptions{Prompt: "Tell me a short story about a robot."})
}

Set Streaming: copilot.Bool(true) in SessionConfig (lines 78-80 of client.go) to receive AssistantMessageDeltaData events for each token chunk.

Connecting to Custom Providers (Ollama)

Use alternative LLM providers by configuring the Provider field in SessionConfig:

func main() {
	client := copilot.NewClient(nil)
	client.Start(context.Background())
	defer client.Stop()

	s, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
		Model: "llama2:7b",
		Provider: &copilot.ProviderConfig{
			Type:    "openai",               // Ollama's compatible endpoint
			BaseURL: "http://localhost:11434/v1",
			// APIKey omitted for local Ollama
		},
		OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer s.Disconnect()

	s.Send(context.Background(),
		copilot.MessageOptions{Prompt: "Write a haiku about spring."})
}

The ProviderConfig struct (lines 1017-1022 of client.go) supports Type, BaseURL, and APIKey fields for OpenAI-compatible endpoints.

Handling UI Elicitation Requests

Implement interactive workflows by responding to user input requests:

func main() {
	client := copilot.NewClient(nil)
	client.Start(context.Background())
	defer client.Stop()

	s, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
		Model: "gpt-4",
		OnUserInputRequest: func(req copilot.UserInputRequest, inv copilot.UserInputInvocation) (copilot.UserInputResponse, error) {
			// In production, replace with actual user input collection
			answer := "yes"
			return copilot.UserInputResponse{
				Answer:      answer,
				WasFreeform: true,
			}, nil
		},
		OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer s.Disconnect()

	s.Send(context.Background(),
		copilot.MessageOptions{Prompt: "Ask me a question, then answer it."})
}

The OnUserInputRequest handler (lines 86-88 of client.go) integrates with the ask_user tool, processed in session.go lines 555-567.

Embedding the CLI Binary

The SDK supports embedding the Copilot CLI using Go 1.16's embed package. After running go tool bundler, the SDK automatically extracts and executes the embedded binary when NewClient is called with nil options. This approach ensures your application remains self-contained without requiring separate CLI installation. See the embedding implementation in [go/internal/embeddedcli/embeddedcli.go](https://github.com/github/copilot-sdk/blob/main/go/internal/embeddedcli/embeddedcli.go) and the README (lines 85-95) for configuration details.

Summary

  • The GitHub Copilot SDK for Go provides JSON-RPC communication with the Copilot CLI through the Client and Session types in go/client.go and go/session.go.
  • Tool integration uses DefineTool in go/definetool.go for automatic JSON schema generation from Go structs.
  • Streaming responses are enabled via the Streaming flag in SessionConfig, delivering incremental content through AssistantMessageDeltaData events.
  • Custom providers like Ollama are supported through ProviderConfig with OpenAI-compatible endpoints.
  • Permission handling and UI elicitation are configured through handler functions in SessionConfig to control tool execution and user interaction.
  • Binary embedding via the bundler tool creates self-contained executables that extract the CLI at runtime.

Frequently Asked Questions

How does permission handling work in the GitHub Copilot SDK for Go?

The SDK invokes the OnPermissionRequest handler before executing sensitive actions like file writes or shell commands. You can use copilot.PermissionHandler.ApproveAll for automation, or implement custom logic returning rpc.PermissionDecisionAllow, rpc.PermissionDecisionDeny, or rpc.PermissionDecisionEscalate based on your security requirements.

Can I use the GitHub Copilot SDK with Go to connect to local LLMs like Ollama?

Yes, configure the Provider field in SessionConfig with Type: "openai" and BaseURL pointing to your local endpoint (e.g., http://localhost:11434/v1 for Ollama). This allows the SDK to communicate with any OpenAI-compatible API, including local inference servers.

What is the difference between Client and Session in the Copilot Go SDK?

The Client manages the transport layer connection to the Copilot CLI runtime and is responsible for starting and stopping the JSON-RPC communication. A Session represents a single conversation context with its own ID, workspace state, and event handlers, created via Client.CreateSession and supporting multiple concurrent instances per client.

How does the SDK automatically generate JSON schemas for tools?

The DefineTool function in go/definetool.go uses Go reflection to analyze the input struct tags (particularly the jsonschema tag) and generates JSON Schema definitions automatically. This enables type-safe unmarshaling of LLM arguments into Go structs without manual schema writing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →