Repository: cli/cli
File: pkg/cmd/agent-task/capi/job.go (Go)

Code before:

type JobPullRequest struct {
	ID     int `json:"id"`
	Number int `json:"number"`
}

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 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, owner, repo)
	body := map[string]any{
		"problem_statement": problemStatement,
		"event_type":        defaultEventType,
	}
	b, _ := json.Marshal(body)
	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()
	if res.StatusCode != http.StatusCreated && res.StatusCode != http.StatusOK { // accept 201 or 200
		// Attempt to parse error body for message
		var er struct {
			Error struct {
				Message string `json:"message"`
			} `json:"error"`
		}
		_ = json.NewDecoder(res.Body).Decode(&er)
		msg := er.Error.Message
		if msg == "" {

Review comment (security):
Let's make these args path-safe:

```suggestion
	url := fmt.Sprintf("%s/%s/%s", url.PathEscape(jobsBasePathV1), url.PathEscape(owner), url.PathEscape(repo))
```

Code after:
	Login string `json:"login"`
}

type JobPullRequest struct {
	ID     int `json:"id"`
	Number int `json:"number"`
}

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 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))

	payload := &Job{
		ProblemStatement: problemStatement,
		EventType:        defaultEventType,
	}
	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)
	}