Introduces a BaseBranch field to CreateOptions and updates the job creation flow to include the base branch in the payload if specified. The CapiClient and related methods are updated to accept and handle the baseBranch parameter, and tests are added to verify correct behavior when a base branch is provided.
128 lines
3.9 KiB
Go
128 lines
3.9 KiB
Go
package capi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
)
|
|
|
|
const defaultEventType = "gh_cli"
|
|
|
|
// Job represents a coding agent's task. Used to request a new session.
|
|
type Job struct {
|
|
ID string `json:"job_id,omitempty"`
|
|
SessionID string `json:"session_id,omitempty"`
|
|
ProblemStatement string `json:"problem_statement,omitempty"`
|
|
EventType string `json:"event_type,omitempty"`
|
|
ContentFilterMode string `json:"content_filter_mode,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
Result string `json:"result,omitempty"`
|
|
Actor *JobActor `json:"actor,omitempty"`
|
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
|
PullRequest *JobPullRequest `json:"pull_request,omitempty"`
|
|
WorkflowRun *struct {
|
|
ID string `json:"id"`
|
|
} `json:"workflow_run,omitempty"`
|
|
ErrorInfo *JobError `json:"error,omitempty"`
|
|
}
|
|
|
|
type JobActor struct {
|
|
ID int `json:"id"`
|
|
Login string `json:"login"`
|
|
}
|
|
|
|
type JobPullRequest struct {
|
|
ID int `json:"id"`
|
|
Number int `json:"number"`
|
|
BaseRef string `json:"base_ref,omitempty"`
|
|
}
|
|
|
|
type JobError struct {
|
|
Message string `json:"message"`
|
|
ResponseStatusCode int `json:"response_status_code,string"`
|
|
Service string `json:"service"`
|
|
}
|
|
|
|
const jobsBasePathV1 = baseCAPIURL + "/agents/swe/v1/jobs"
|
|
|
|
// CreateJob queues a new job using the v1 Jobs API. It may or may not
|
|
// return Pull Request information. If Pull Request information is required
|
|
// following up by polling GetJob with the job ID is necessary.
|
|
func (c *CAPIClient) CreateJob(ctx context.Context, owner, repo, problemStatement, baseBranch string) (*Job, error) {
|
|
if owner == "" || repo == "" {
|
|
return nil, errors.New("owner and repo are required")
|
|
}
|
|
if problemStatement == "" {
|
|
return nil, errors.New("problem statement is required")
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/%s/%s", jobsBasePathV1, url.PathEscape(owner), url.PathEscape(repo))
|
|
|
|
prOpts := JobPullRequest{}
|
|
if baseBranch != "" {
|
|
prOpts.BaseRef = "refs/heads/" + baseBranch
|
|
}
|
|
|
|
payload := &Job{
|
|
ProblemStatement: problemStatement,
|
|
EventType: defaultEventType,
|
|
PullRequest: &prOpts,
|
|
}
|
|
|
|
b, _ := json.Marshal(payload)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(b))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
res, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
var j Job
|
|
if err := json.NewDecoder(res.Body).Decode(&j); err != nil {
|
|
return nil, fmt.Errorf("failed to decode create job response: %w", err)
|
|
}
|
|
|
|
if res.StatusCode != http.StatusCreated && res.StatusCode != http.StatusOK { // accept 201 or 200
|
|
return nil, fmt.Errorf("failed to create job: %s", j.ErrorInfo.Message)
|
|
}
|
|
|
|
return &j, nil
|
|
}
|
|
|
|
// GetJob retrieves a agent job
|
|
func (c *CAPIClient) GetJob(ctx context.Context, owner, repo, jobID string) (*Job, error) {
|
|
if owner == "" || repo == "" || jobID == "" {
|
|
return nil, errors.New("owner, repo, and jobID are required")
|
|
}
|
|
url := fmt.Sprintf("%s/%s/%s/%s", jobsBasePathV1, url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(jobID))
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
// Normalize to "<code> <text>" form
|
|
statusText := fmt.Sprintf("%d %s", res.StatusCode, http.StatusText(res.StatusCode))
|
|
return nil, fmt.Errorf("failed to get job: %s", statusText)
|
|
}
|
|
var j Job
|
|
if err := json.NewDecoder(res.Body).Decode(&j); err != nil {
|
|
return nil, fmt.Errorf("failed to decode get job response: %w", err)
|
|
}
|
|
return &j, nil
|
|
}
|