cli/pkg/cmd/agent-task/capi/client.go
Kynan Ware 78b958f9ae
fix(agent-task): resolve Copilot API URL dynamically (#12956)
* fix(agent-task): resolve Copilot API URL dynamically

Query viewer.copilotEndpoints.api to get the correct Copilot API URL
for the user's host instead of hardcoding api.githubcopilot.com. This
fixes 401 errors for ghe.com tenancy users whose Copilot API lives at
a different endpoint.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-18 18:14:02 +00:00

77 lines
2.6 KiB
Go

package capi
import (
"context"
"net/http"
"net/url"
)
//go:generate moq -rm -out client_mock.go . CapiClient
// CapiClient defines the methods used by the caller. Implementations
// may be replaced with test doubles in unit tests.
type CapiClient interface {
ListLatestSessionsForViewer(ctx context.Context, limit int) ([]*Session, error)
CreateJob(ctx context.Context, owner, repo, problemStatement, baseBranch string, customAgent string) (*Job, error)
GetJob(ctx context.Context, owner, repo, jobID string) (*Job, error)
GetSession(ctx context.Context, id string) (*Session, error)
GetSessionLogs(ctx context.Context, id string) ([]byte, error)
ListSessionsByResourceID(ctx context.Context, resourceType string, resourceID int64, limit int) ([]*Session, error)
GetPullRequestDatabaseID(ctx context.Context, hostname string, owner string, repo string, number int) (int64, string, error)
}
// CAPIClient is a client for interacting with the Copilot API
type CAPIClient struct {
httpClient *http.Client
host string
capiBaseURL string
}
// NewCAPIClient creates a new CAPI client. Provide a token, the user's GitHub
// host, the resolved Copilot API URL, and an HTTP client which will be used as
// the base transport for CAPI requests.
//
// The provided HTTP client will be mutated for use with CAPI, so it should not
// be reused elsewhere.
func NewCAPIClient(httpClient *http.Client, token string, host string, capiBaseURL string) *CAPIClient {
httpClient.Transport = newCAPITransport(token, capiBaseURL, httpClient.Transport)
return &CAPIClient{
httpClient: httpClient,
host: host,
capiBaseURL: capiBaseURL,
}
}
// capiTransport adds the Copilot auth headers
type capiTransport struct {
rp http.RoundTripper
token string
capiHost string
}
func newCAPITransport(token string, capiBaseURL string, rp http.RoundTripper) *capiTransport {
capiHost := ""
if u, err := url.Parse(capiBaseURL); err == nil {
capiHost = u.Host
}
return &capiTransport{
rp: rp,
token: token,
capiHost: capiHost,
}
}
func (ct *capiTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+ct.token)
// Since this RoundTrip is reused for both Copilot API and
// GitHub API requests, we conditionally add the integration
// ID only when performing requests to the Copilot API.
if req.URL.Host == ct.capiHost {
req.Header.Add("Copilot-Integration-Id", "copilot-4-cli")
// Ensure we are not using GitHub API versions while targeting CAPI.
req.Header.Set("X-GitHub-Api-Version", "2026-01-09")
}
return ct.rp.RoundTrip(req)
}