Skip to content

Integrate Copilot SDK (Go) — internal/copilot package + gh devlake insights #63

@ewega

Description

@ewega

Problem

DevLake aggregates data from multiple DevOps tools into a unified data model, but the only way to gain insights is through pre-built Grafana dashboards. Users cannot ask ad-hoc, cross-tool questions like "Which repos had the highest lead time for changes last month?" or "Is there a correlation between Copilot adoption and deployment frequency?" without writing SQL or building custom dashboards.

Proposed Solution

Integrate the GitHub Copilot SDK (Go) into gh-devlake and add gh devlake insights — a natural language Q&A command that reasons over DevLake's aggregated data.

Command surface

# One-shot question
gh devlake insights "Which repos had the highest lead time last month?"

# Interactive mode (multi-turn conversation)
gh devlake insights
> Which teams deploy most frequently?
> Compare that with their change failure rate
> exit

Architecture

internal/
  copilot/
    client.go     # Copilot SDK client lifecycle (start/stop, graceful error if CLI missing)
    tools.go      # Custom tool definitions (DefineTool wrappers)
    system.go     # System message with DevLake context
cmd/
  insights.go     # gh devlake insights command

SDK client lifecycle (internal/copilot/client.go)

package copilot

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

// NewCopilotClient creates and starts a Copilot SDK client.
// Returns a user-friendly error if Copilot CLI is not installed.
func NewCopilotClient(ctx context.Context) (*copilot.Client, error) {
    client := copilot.NewClient(&copilot.ClientOptions{
        LogLevel: "error",
    })
    if err := client.Start(ctx); err != nil {
        // Check if it's a "copilot not found" error
        return nil, fmt.Errorf("Copilot CLI is required for this command.\n" +
            "Install: https://docs.github.com/en/copilot/how-tos/copilot-cli/install-copilot-cli")
    }
    return client, nil
}

Design decision: Copilot CLI must be installed as a prerequisite. No embedded bundling for now — keeps the binary small and avoids version coupling.

Custom tools (internal/copilot/tools.go)

Using the SDK's DefineTool for type-safe tool registration:

// Tool: query_dora_metrics
type DoraParams struct {
    Project   string `json:"project" jsonschema:"DevLake project name"`
    Timeframe string `json:"timeframe,omitempty" jsonschema:"Time range, e.g. 30d, 7d. Default: 30d"`
}

var queryDoraTool = copilot.DefineTool("query_dora_metrics",
    "Query DORA metrics (deployment frequency, lead time, change failure rate, MTTR) for a DevLake project",
    func(params DoraParams, inv copilot.ToolInvocation) (any, error) {
        engine := query.NewEngine(dbURL)
        result, err := engine.Execute("dora", map[string]string{
            "project":   params.Project,
            "timeframe": params.Timeframe,
        })
        return result, err
    })

// Tool: query_copilot_usage
type CopilotParams struct {
    Project   string `json:"project" jsonschema:"DevLake project name"`
    Timeframe string `json:"timeframe,omitempty" jsonschema:"Time range. Default: 30d"`
}

var queryCopilotTool = copilot.DefineTool("query_copilot_usage",
    "Query GitHub Copilot usage metrics (seats, acceptance rate, languages, editors) for a DevLake project",
    func(params CopilotParams, inv copilot.ToolInvocation) (any, error) {
        engine := query.NewEngine(dbURL)
        return engine.Execute("copilot", map[string]string{
            "project":   params.Project,
            "timeframe": params.Timeframe,
        })
    })

// Tool: list_connections
var listConnectionsTool = copilot.DefineTool("list_connections",
    "List all configured DevLake plugin connections with their health status",
    func(params struct{}, inv copilot.ToolInvocation) (any, error) {
        client := devlake.NewClient(apiURL)
        // ... list connections + test each ...
    })

// Tool: check_health
var checkHealthTool = copilot.DefineTool("check_health",
    "Check health of all DevLake endpoints and connections",
    func(params struct{}, inv copilot.ToolInvocation) (any, error) {
        client := devlake.NewClient(apiURL)
        return client.Health()
    })

// Tool: get_pipeline_status
type PipelineParams struct {
    Limit int `json:"limit,omitempty" jsonschema:"Max pipelines to return. Default: 10"`
}

var getPipelinesTool = copilot.DefineTool("get_pipeline_status",
    "Get recent DevLake pipeline runs and their status (completed, failed, running)",
    func(params PipelineParams, inv copilot.ToolInvocation) (any, error) {
        client := devlake.NewClient(apiURL)
        // ... list recent pipelines ...
    })

The tools reuse the query engine from #62 and the existing DevLake API client from internal/devlake/client.go.

Session configuration

session, err := client.CreateSession(ctx, &copilot.SessionConfig{
    Model:     "gpt-4.1",  // or let user override with --model
    Streaming: true,
    Tools: []copilot.Tool{
        queryDoraTool,
        queryCopilotTool,
        listConnectionsTool,
        checkHealthTool,
        getPipelinesTool,
    },
    SystemMessage: &copilot.SystemMessageConfig{
        Content: systemPrompt, // DevLake-specific context
    },
    OnUserInputRequest: func(req copilot.UserInputRequest, inv copilot.UserInputInvocation) (copilot.UserInputResponse, error) {
        // Enable ask_user tool for clarifying questions
        fmt.Printf("  %s\n", req.Question)
        answer := prompt.Input("  > ")
        return copilot.UserInputResponse{Answer: answer, WasFreeform: true}, nil
    },
})

Streaming output

session.On(func(event copilot.SessionEvent) {
    if event.Type == "assistant.message_delta" {
        if event.Data.DeltaContent != nil {
            fmt.Print(*event.Data.DeltaContent)
        }
    }
    if event.Type == "session.idle" {
        fmt.Println() // newline when done
    }
})

Auth model — separate concerns

Auth Purpose Resolution
Copilot CLI auth Model access (GPT-4, Claude, etc.) Copilot CLI handles this — user must be signed in (copilot auth login)
DevLake PAT Data access (API calls, connections) Existing token resolution chain (flag → envfile → env → prompt)
DB connection Query execution State file auto-detection or --db-url flag

No token sharing between the two auth domains.

Cost transparency

On first use, print a one-time notice:

💡 Insights uses GitHub Copilot to analyze DevLake data.
   Each prompt counts against your Copilot premium request quota.
   For cost-free queries, use: gh devlake query dora --project my-team

This notice is shown once and suppressed thereafter (tracked via a flag in the state file or a dot file).

Command flags

gh devlake insights [question]
  --model     Model to use (default: gpt-4.1)
  --project   Default project for queries (avoids repeated prompting)
  --db-url    Database connection URL (auto-detected if omitted)

Files to create/modify

File Change
internal/copilot/client.go NEW — SDK client lifecycle
internal/copilot/tools.go NEW — custom tool definitions
internal/copilot/system.go NEW — system message with DevLake context
cmd/insights.go NEW — gh devlake insights command
go.mod Add github.com/github/copilot-sdk/go dependency

Acceptance Criteria

  • gh devlake insights "question" sends a prompt and streams the response
  • gh devlake insights enters interactive multi-turn mode
  • Custom tools (query_dora_metrics, query_copilot_usage, list_connections, check_health, get_pipeline_status) are registered and functional
  • LLM can compose multiple tool calls to answer cross-cutting questions
  • Graceful error if Copilot CLI is not installed
  • First-use cost transparency notice is displayed
  • Auth is separate: Copilot for models, PAT for DevLake, DB URL for queries
  • Streaming output for progressive terminal rendering
  • go build ./... and go test ./... pass
  • README updated

Target Version

v0.4.x — AI-powered insights over DevLake data.

Dependencies

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions