chore: promote tasks to stable from experimental (#20921)

- Promote tasks from `/api/experimental` to `/api/v2`.
- Move sdk from `ExperimentalClient` to `Client`.
- Update swagger
This commit is contained in:
Danielle Maywood
2025-11-25 15:24:25 +00:00
committed by GitHub
parent 37fc6646ad
commit b255827a52
23 changed files with 903 additions and 838 deletions
+20 -60
View File
@@ -28,8 +28,6 @@ import (
const AITaskPromptParameterName = provider.TaskPromptParameterName
// CreateTaskRequest represents the request to create a new task.
//
// Experimental: This type is experimental and may change in the future.
type CreateTaskRequest struct {
TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"`
TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"`
@@ -39,10 +37,8 @@ type CreateTaskRequest struct {
}
// CreateTask creates a new task.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) CreateTask(ctx context.Context, user string, request CreateTaskRequest) (Task, error) {
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/experimental/tasks/%s", user), request)
func (c *Client) CreateTask(ctx context.Context, user string, request CreateTaskRequest) (Task, error) {
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/tasks/%s", user), request)
if err != nil {
return Task{}, err
}
@@ -61,8 +57,6 @@ func (c *ExperimentalClient) CreateTask(ctx context.Context, user string, reques
}
// TaskStatus represents the status of a task.
//
// Experimental: This type is experimental and may change in the future.
type TaskStatus string
const (
@@ -99,8 +93,6 @@ func AllTaskStatuses() []TaskStatus {
}
// TaskState represents the high-level lifecycle of a task.
//
// Experimental: This type is experimental and may change in the future.
type TaskState string
// TaskState enums.
@@ -120,8 +112,6 @@ const (
)
// Task represents a task.
//
// Experimental: This type is experimental and may change in the future.
type Task struct {
ID uuid.UUID `json:"id" format:"uuid" table:"id"`
OrganizationID uuid.UUID `json:"organization_id" format:"uuid" table:"organization id"`
@@ -151,8 +141,6 @@ type Task struct {
}
// TaskStateEntry represents a single entry in the task's state history.
//
// Experimental: This type is experimental and may change in the future.
type TaskStateEntry struct {
Timestamp time.Time `json:"timestamp" format:"date-time" table:"-"`
State TaskState `json:"state" enum:"working,idle,completed,failed" table:"state"`
@@ -161,8 +149,6 @@ type TaskStateEntry struct {
}
// TasksFilter filters the list of tasks.
//
// Experimental: This type is experimental and may change in the future.
type TasksFilter struct {
// Owner can be a username, UUID, or "me".
Owner string `json:"owner,omitempty"`
@@ -175,8 +161,6 @@ type TasksFilter struct {
}
// TaskListResponse is the response shape for tasks list.
//
// Experimental response shape for tasks list (server returns []Task).
type TasksListResponse struct {
Tasks []Task `json:"tasks"`
Count int `json:"count"`
@@ -208,14 +192,12 @@ func (f TasksFilter) asRequestOption() RequestOption {
}
// Tasks lists all tasks belonging to the user or specified owner.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) Tasks(ctx context.Context, filter *TasksFilter) ([]Task, error) {
func (c *Client) Tasks(ctx context.Context, filter *TasksFilter) ([]Task, error) {
if filter == nil {
filter = &TasksFilter{}
}
res, err := c.Request(ctx, http.MethodGet, "/api/experimental/tasks", nil, filter.asRequestOption())
res, err := c.Request(ctx, http.MethodGet, "/api/v2/tasks", nil, filter.asRequestOption())
if err != nil {
return nil, err
}
@@ -232,12 +214,10 @@ func (c *ExperimentalClient) Tasks(ctx context.Context, filter *TasksFilter) ([]
return tres.Tasks, nil
}
// TaskByID fetches a single experimental task by its ID.
// TaskByID fetches a single task by its ID.
// Only tasks owned by codersdk.Me are supported.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) TaskByID(ctx context.Context, id uuid.UUID) (Task, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/experimental/tasks/%s/%s", "me", id.String()), nil)
func (c *Client) TaskByID(ctx context.Context, id uuid.UUID) (Task, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/tasks/%s/%s", "me", id.String()), nil)
if err != nil {
return Task{}, err
}
@@ -254,14 +234,12 @@ func (c *ExperimentalClient) TaskByID(ctx context.Context, id uuid.UUID) (Task,
return task, nil
}
// TaskByOwnerAndName fetches a single experimental task by its owner and name.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) TaskByOwnerAndName(ctx context.Context, owner, ident string) (Task, error) {
// TaskByOwnerAndName fetches a single task by its owner and name.
func (c *Client) TaskByOwnerAndName(ctx context.Context, owner, ident string) (Task, error) {
if owner == "" {
owner = Me
}
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/experimental/tasks/%s/%s", owner, ident), nil)
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/tasks/%s/%s", owner, ident), nil)
if err != nil {
return Task{}, err
}
@@ -300,7 +278,7 @@ func splitTaskIdentifier(identifier string) (owner string, taskName string, err
//
// Since there is no TaskByOwnerAndName endpoint yet, this function uses the
// list endpoint with filtering when a name is provided.
func (c *ExperimentalClient) TaskByIdentifier(ctx context.Context, identifier string) (Task, error) {
func (c *Client) TaskByIdentifier(ctx context.Context, identifier string) (Task, error) {
identifier = strings.TrimSpace(identifier)
// Try parsing as UUID first.
@@ -318,10 +296,8 @@ func (c *ExperimentalClient) TaskByIdentifier(ctx context.Context, identifier st
}
// DeleteTask deletes a task by its ID.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) DeleteTask(ctx context.Context, user string, id uuid.UUID) error {
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/experimental/tasks/%s/%s", user, id.String()), nil)
func (c *Client) DeleteTask(ctx context.Context, user string, id uuid.UUID) error {
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/tasks/%s/%s", user, id.String()), nil)
if err != nil {
return err
}
@@ -333,17 +309,13 @@ func (c *ExperimentalClient) DeleteTask(ctx context.Context, user string, id uui
}
// TaskSendRequest is used to send task input to the tasks sidebar app.
//
// Experimental: This type is experimental and may change in the future.
type TaskSendRequest struct {
Input string `json:"input"`
}
// TaskSend submits task input to the tasks sidebar app.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) TaskSend(ctx context.Context, user string, id uuid.UUID, req TaskSendRequest) error {
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/experimental/tasks/%s/%s/send", user, id.String()), req)
func (c *Client) TaskSend(ctx context.Context, user string, id uuid.UUID, req TaskSendRequest) error {
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/tasks/%s/%s/send", user, id.String()), req)
if err != nil {
return err
}
@@ -355,17 +327,13 @@ func (c *ExperimentalClient) TaskSend(ctx context.Context, user string, id uuid.
}
// UpdateTaskInputRequest is used to update a task's input.
//
// Experimental: This type is experimental and may change in the future.
type UpdateTaskInputRequest struct {
Input string `json:"input"`
}
// UpdateTaskInput updates the task's input.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) UpdateTaskInput(ctx context.Context, user string, id uuid.UUID, req UpdateTaskInputRequest) error {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/experimental/tasks/%s/%s/input", user, id.String()), req)
func (c *Client) UpdateTaskInput(ctx context.Context, user string, id uuid.UUID, req UpdateTaskInputRequest) error {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/tasks/%s/%s/input", user, id.String()), req)
if err != nil {
return err
}
@@ -377,8 +345,6 @@ func (c *ExperimentalClient) UpdateTaskInput(ctx context.Context, user string, i
}
// TaskLogType indicates the source of a task log entry.
//
// Experimental: This type is experimental and may change in the future.
type TaskLogType string
// TaskLogType enums.
@@ -388,8 +354,6 @@ const (
)
// TaskLogEntry represents a single log entry for a task.
//
// Experimental: This type is experimental and may change in the future.
type TaskLogEntry struct {
ID int `json:"id" table:"id"`
Content string `json:"content" table:"content"`
@@ -398,17 +362,13 @@ type TaskLogEntry struct {
}
// TaskLogsResponse contains the logs for a task.
//
// Experimental: This type is experimental and may change in the future.
type TaskLogsResponse struct {
Logs []TaskLogEntry `json:"logs"`
}
// TaskLogs retrieves logs from the task's sidebar app via the experimental API.
//
// Experimental: This method is experimental and may change in the future.
func (c *ExperimentalClient) TaskLogs(ctx context.Context, user string, id uuid.UUID) (TaskLogsResponse, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/experimental/tasks/%s/%s/logs", user, id.String()), nil)
// TaskLogs retrieves logs from the task app.
func (c *Client) TaskLogs(ctx context.Context, user string, id uuid.UUID) (TaskLogsResponse, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/tasks/%s/%s/logs", user, id.String()), nil)
if err != nil {
return TaskLogsResponse{}, err
}
+9 -19
View File
@@ -1899,8 +1899,7 @@ var CreateTask = Tool[CreateTaskArgs, codersdk.Task]{
args.User = codersdk.Me
}
expClient := codersdk.NewExperimentalClient(deps.coderClient)
task, err := expClient.CreateTask(ctx, args.User, codersdk.CreateTaskRequest{
task, err := deps.coderClient.CreateTask(ctx, args.User, codersdk.CreateTaskRequest{
Input: args.Input,
TemplateVersionID: tvID,
TemplateVersionPresetID: tvPresetID,
@@ -1937,14 +1936,12 @@ var DeleteTask = Tool[DeleteTaskArgs, codersdk.Response]{
return codersdk.Response{}, xerrors.New("task_id is required")
}
expClient := codersdk.NewExperimentalClient(deps.coderClient)
task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
task, err := deps.coderClient.TaskByIdentifier(ctx, args.TaskID)
if err != nil {
return codersdk.Response{}, xerrors.Errorf("resolve task: %w", err)
}
err = expClient.DeleteTask(ctx, task.OwnerName, task.ID)
err = deps.coderClient.DeleteTask(ctx, task.OwnerName, task.ID)
if err != nil {
return codersdk.Response{}, xerrors.Errorf("delete task: %w", err)
}
@@ -1988,8 +1985,7 @@ var ListTasks = Tool[ListTasksArgs, ListTasksResponse]{
args.User = codersdk.Me
}
expClient := codersdk.NewExperimentalClient(deps.coderClient)
tasks, err := expClient.Tasks(ctx, &codersdk.TasksFilter{
tasks, err := deps.coderClient.Tasks(ctx, &codersdk.TasksFilter{
Owner: args.User,
Status: args.Status,
})
@@ -2032,9 +2028,7 @@ var GetTaskStatus = Tool[GetTaskStatusArgs, GetTaskStatusResponse]{
return GetTaskStatusResponse{}, xerrors.New("task_id is required")
}
expClient := codersdk.NewExperimentalClient(deps.coderClient)
task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
task, err := deps.coderClient.TaskByIdentifier(ctx, args.TaskID)
if err != nil {
return GetTaskStatusResponse{}, xerrors.Errorf("resolve task %q: %w", args.TaskID, err)
}
@@ -2079,14 +2073,12 @@ var SendTaskInput = Tool[SendTaskInputArgs, codersdk.Response]{
return codersdk.Response{}, xerrors.New("input is required")
}
expClient := codersdk.NewExperimentalClient(deps.coderClient)
task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
task, err := deps.coderClient.TaskByIdentifier(ctx, args.TaskID)
if err != nil {
return codersdk.Response{}, xerrors.Errorf("resolve task %q: %w", args.TaskID, err)
}
err = expClient.TaskSend(ctx, task.OwnerName, task.ID, codersdk.TaskSendRequest{
err = deps.coderClient.TaskSend(ctx, task.OwnerName, task.ID, codersdk.TaskSendRequest{
Input: args.Input,
})
if err != nil {
@@ -2123,14 +2115,12 @@ var GetTaskLogs = Tool[GetTaskLogsArgs, codersdk.TaskLogsResponse]{
return codersdk.TaskLogsResponse{}, xerrors.New("task_id is required")
}
expClient := codersdk.NewExperimentalClient(deps.coderClient)
task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
task, err := deps.coderClient.TaskByIdentifier(ctx, args.TaskID)
if err != nil {
return codersdk.TaskLogsResponse{}, err
}
logs, err := expClient.TaskLogs(ctx, task.OwnerName, task.ID)
logs, err := deps.coderClient.TaskLogs(ctx, task.OwnerName, task.ID)
if err != nil {
return codersdk.TaskLogsResponse{}, xerrors.Errorf("get task logs %q: %w", args.TaskID, err)
}
+2 -5
View File
@@ -1025,11 +1025,8 @@ func TestTools(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
taskExpClient := codersdk.NewExperimentalClient(taskClient)
// This task should not show up since listing is user-scoped.
_, err := expClient.CreateTask(ctx, member.Username, codersdk.CreateTaskRequest{
_, err := client.CreateTask(ctx, member.Username, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "task for member",
Name: "list-task-workspace-member",
@@ -1039,7 +1036,7 @@ func TestTools(t *testing.T) {
// Create tasks for taskUser. These should show up in the list.
for i := range 5 {
taskName := fmt.Sprintf("list-task-workspace-%d", i)
task, err := taskExpClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := taskClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: fmt.Sprintf("task %d", i),
Name: taskName,