Reusable Go package for integrating with the GitHub Copilot CLI SDK via a Kubernetes sidecar pattern.
Warning: The Copilot CLI SDK is in Technical Preview (v0.1.x) and may not yet be suitable for production use.
┌─────────────────────────────────────────────────────┐
│ Kubernetes Pod │
│ │
│ ┌───────────────────┐ ┌──────────────────────┐ │
│ │ Your Go Service │ │ copilot-cli sidecar │ │
│ │ │ │ │ │
│ │ copilotcli │────▶│ copilot --headless │ │
│ │ (this package) │TCP │ --port 4321 │ │
│ │ │4321 │ --no-auto-update │ │
│ │ POST /api/copilot │ │ │ │
│ │ /query │ │ │ │
│ └───────────────────┘ └──────┬───────────────┘ │
│ ↑ │ │
│ Ingress/SVC HTTPS (outbound) │
│ ↓ │
│ GitHub API / LLM Provider │
└─────────────────────────────────────────────────────┘
The Copilot CLI runs as a headless JSON-RPC server in a sidecar container. This package connects to it over localhost:4321 (pod-internal TCP), sends prompts, and receives LLM responses. Custom tools execute in-process in your Go service.
go get github.com/github/copilot-sdk/goThere is no separate installation for this package — add it to your module and it imports the SDK as a dependency.
go get github.com/kazan/copilotcliThe package uses functional options — no config struct, no environment reading.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/kazan/copilotcli"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
// Create client with functional options
client, err := copilotcli.New(
copilotcli.WithCLIURL("localhost:4321"), // default
copilotcli.WithModel("gpt-4o"), // default
copilotcli.WithStreaming(true),
copilotcli.WithConnTimeout(15*time.Second),
copilotcli.WithSystemMessage("You are an inventory management assistant."),
copilotcli.WithTools(copilotcli.ToolDefinition{
Name: "check_stock",
Description: "Check inventory stock level for a given SKU",
Parameters: []copilotcli.ToolParameter{
{Name: "sku", Type: "string", Description: "Product SKU", Required: true},
},
Handler: func(args map[string]any) (string, error) {
sku := args["sku"].(string)
return `{"sku": "` + sku + `", "quantity": 42, "available": true}`, nil
},
}),
)
if err != nil {
log.Fatalf("creating copilot client: %v", err)
}
// Connect to sidecar (with retry + exponential backoff)
if err := client.Start(ctx); err != nil {
log.Fatalf("connecting to copilot sidecar: %v", err)
}
defer client.Stop()
// Register HTTP handlers
mux := http.NewServeMux()
mux.HandleFunc("POST /api/copilot/query", copilotcli.NewQueryHandler(client))
mux.HandleFunc("POST /api/copilot/stream", copilotcli.NewStreamHandler(client))
mux.HandleFunc("GET /api/copilot/health", copilotcli.NewHealthHandler(client))
// Start server...
}# Simple query
curl -X POST http://localhost:8080/api/copilot/query \
-H 'Content-Type: application/json' \
-d '{"prompt": "What is the stock level for SKU ABC123?"}'
# Response:
# {"content": "The stock level for SKU ABC123 is 42 units, and it is currently available.", "session_id": "sess_abc123"}
# Multi-turn conversation
curl -X POST http://localhost:8080/api/copilot/query \
-H 'Content-Type: application/json' \
-d '{"prompt": "And what about SKU DEF456?", "session_id": "sess_abc123"}'
# Streaming (SSE)
curl -X POST http://localhost:8080/api/copilot/stream \
-H 'Content-Type: application/json' \
-d '{"prompt": "Summarize inventory status"}'Requires a GitHub Copilot subscription. Set GITHUB_TOKEN on the sidecar container:
- name: copilot-sidecar
env:
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: copilot-secrets
key: github-tokenNo special option needed on the client — GitHub auth is the default:
client, err := copilotcli.New() // uses WithGitHubAuth() by defaultNo GitHub auth needed. Configure the provider via options:
client, err := copilotcli.New(
copilotcli.WithBYOK(copilotcli.ProviderOpenAI, "https://api.openai.com/v1", "sk-xxx"),
copilotcli.WithModel("gpt-4"),
)For Azure OpenAI:
client, err := copilotcli.New(
copilotcli.WithBYOK(copilotcli.ProviderAzure, "https://my-resource.openai.azure.com", "azure-key"),
copilotcli.WithAzureAPIVersion("2024-10-21"),
)See example/deployment.yaml for a complete reference manifest.
Key points:
- Sidecar listens on
localhost:4321(pod-internal, no Service/Ingress needed) - App connects via
COPILOT_CLI_URL=localhost:4321 - TCP readiness probe on port 4321 ensures sidecar is ready
- Network policy allows HTTPS egress to GitHub API / LLM providers
- Use
--no-auto-updateto prevent unexpected CLI updates in production
# Start the sidecar locally
docker compose -f example/docker-compose.copilot.yml up
# Or build and run manually
docker build -t copilot-cli-sidecar:latest -f example/Dockerfile.copilot-sidecar .
docker run -p 4321:4321 -e GITHUB_TOKEN=$GITHUB_TOKEN copilot-cli-sidecar:latestTools are functions that execute in your Go service (not in the sidecar). The LLM decides when to call them based on the tool description and the user's prompt.
tools := []copilotcli.ToolDefinition{
{
Name: "lookup_order",
Description: "Look up order details by order ID",
Parameters: []copilotcli.ToolParameter{
{Name: "order_id", Type: "string", Description: "Order identifier", Required: true},
},
Handler: func(args map[string]any) (string, error) {
orderID := args["order_id"].(string)
order, err := orderService.Get(orderID)
if err != nil {
return "", err
}
data, _ := json.Marshal(order)
return string(data), nil
},
},
}For type-safe tools with automatic JSON schema generation:
type StockParams struct {
SKU string `json:"sku" jsonschema:"Product SKU"`
Warehouse string `json:"warehouse" jsonschema:"Warehouse code"`
}
stockTool := copilotcli.DefineTypedTool("check_stock", "Check stock in a specific warehouse",
func(params StockParams, inv copilot.ToolInvocation) (any, error) {
return inventoryService.CheckStock(params.SKU, params.Warehouse)
},
)| Limitation | Details |
|---|---|
| Technical Preview | SDK v0.1.x — API may change, not production-grade |
| No official CLI image | Must build your own sidecar Docker image |
| Protocol version pinning | SDK and CLI must match protocol version (currently v2) |
| Copilot subscription | Required for GitHub auth mode; BYOK avoids this |
| Billing | Each prompt counts toward premium request quota (GitHub auth) |
| CLI auto-updates | Must use --no-auto-update in production |
| No Windows sidecar | Sidecar pattern requires Linux containers |
copilotcli/
├── config.go # Internal cfg struct, defaults, auth/provider types
├── options.go # Functional options (WithCLIURL, WithModel, WithBYOK, etc.)
├── client.go # Core client: New, Start, Stop, Query, QueryStream
├── tools.go # Tool definitions and SDK conversion
├── handler.go # HTTP handlers (query, stream SSE, health)
├── errors.go # Sentinel errors
├── example/ # Kubernetes & Docker deployment examples
└── README.md # This file