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
+2 -3
View File
@@ -111,8 +111,7 @@ func (r *RootCmd) taskCreate() *serpent.Command {
}
var (
ctx = inv.Context()
expClient = codersdk.NewExperimentalClient(client)
ctx = inv.Context()
taskInput string
templateVersionID uuid.UUID
@@ -208,7 +207,7 @@ func (r *RootCmd) taskCreate() *serpent.Command {
templateVersionPresetID = preset.ID
}
task, err := expClient.CreateTask(ctx, ownerArg, codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, ownerArg, codersdk.CreateTaskRequest{
Name: taskName,
TemplateVersionID: templateVersionID,
TemplateVersionPresetID: templateVersionPresetID,
+1 -1
View File
@@ -69,7 +69,7 @@ func TestTaskCreate(t *testing.T) {
ActiveVersionID: templateVersionID,
},
})
case fmt.Sprintf("/api/experimental/tasks/%s", username):
case fmt.Sprintf("/api/v2/tasks/%s", username):
var req codersdk.CreateTaskRequest
if !httpapi.Read(ctx, w, r, &req) {
return
+2 -3
View File
@@ -44,11 +44,10 @@ func (r *RootCmd) taskDelete() *serpent.Command {
if err != nil {
return err
}
exp := codersdk.NewExperimentalClient(client)
var tasks []codersdk.Task
for _, identifier := range inv.Args {
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return xerrors.Errorf("resolve task %q: %w", identifier, err)
}
@@ -71,7 +70,7 @@ func (r *RootCmd) taskDelete() *serpent.Command {
for i, task := range tasks {
display := displayList[i]
if err := exp.DeleteTask(ctx, task.OwnerName, task.ID); err != nil {
if err := client.DeleteTask(ctx, task.OwnerName, task.ID); err != nil {
return xerrors.Errorf("delete task %q: %w", display, err)
}
_, _ = fmt.Fprintln(
+11 -11
View File
@@ -56,7 +56,7 @@ func TestExpTaskDelete(t *testing.T) {
taskID := uuid.MustParse(id1)
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/exists":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/exists":
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK,
codersdk.Task{
@@ -64,7 +64,7 @@ func TestExpTaskDelete(t *testing.T) {
Name: "exists",
OwnerName: "me",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id1:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id1:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
default:
@@ -82,13 +82,13 @@ func TestExpTaskDelete(t *testing.T) {
buildHandler: func(c *testCounters) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/"+id2:
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/"+id2:
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse(id2),
OwnerName: "me",
Name: "uuid-task",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id2:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id2:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
default:
@@ -104,24 +104,24 @@ func TestExpTaskDelete(t *testing.T) {
buildHandler: func(c *testCounters) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/first":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/first":
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse(id3),
Name: "first",
OwnerName: "me",
})
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/"+id4:
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/"+id4:
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse(id4),
OwnerName: "me",
Name: "uuid-task-4",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id3:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id3:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id4:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id4:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
default:
@@ -140,7 +140,7 @@ func TestExpTaskDelete(t *testing.T) {
buildHandler: func(_ *testCounters) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks" && r.URL.Query().Get("q") == "owner:\"me\"":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks" && r.URL.Query().Get("q") == "owner:\"me\"":
httpapi.Write(r.Context(), w, http.StatusOK, struct {
Tasks []codersdk.Task `json:"tasks"`
Count int `json:"count"`
@@ -163,14 +163,14 @@ func TestExpTaskDelete(t *testing.T) {
taskID := uuid.MustParse(id5)
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/bad":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/bad":
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: taskID,
Name: "bad",
OwnerName: "me",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/bad":
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/bad":
httpapi.InternalServerError(w, xerrors.New("boom"))
default:
httpapi.InternalServerError(w, xerrors.New("unwanted path: "+r.Method+" "+r.URL.Path))
+1 -2
View File
@@ -135,14 +135,13 @@ func (r *RootCmd) taskList() *serpent.Command {
}
ctx := inv.Context()
exp := codersdk.NewExperimentalClient(client)
targetUser := strings.TrimSpace(user)
if targetUser == "" && !all {
targetUser = codersdk.Me
}
tasks, err := exp.Tasks(ctx, &codersdk.TasksFilter{
tasks, err := client.Tasks(ctx, &codersdk.TasksFilter{
Owner: targetUser,
Status: codersdk.TaskStatus(statusFilter),
})
+2 -3
View File
@@ -41,16 +41,15 @@ func (r *RootCmd) taskLogs() *serpent.Command {
var (
ctx = inv.Context()
exp = codersdk.NewExperimentalClient(client)
identifier = inv.Args[0]
)
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return xerrors.Errorf("resolve task %q: %w", identifier, err)
}
logs, err := exp.TaskLogs(ctx, codersdk.Me, task.ID)
logs, err := client.TaskLogs(ctx, codersdk.Me, task.ID)
if err != nil {
return xerrors.Errorf("get task logs: %w", err)
}
+2 -3
View File
@@ -39,7 +39,6 @@ func (r *RootCmd) taskSend() *serpent.Command {
var (
ctx = inv.Context()
exp = codersdk.NewExperimentalClient(client)
identifier = inv.Args[0]
taskInput string
@@ -60,12 +59,12 @@ func (r *RootCmd) taskSend() *serpent.Command {
taskInput = inv.Args[1]
}
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return xerrors.Errorf("resolve task: %w", err)
}
if err = exp.TaskSend(ctx, codersdk.Me, task.ID, codersdk.TaskSendRequest{Input: taskInput}); err != nil {
if err = client.TaskSend(ctx, codersdk.Me, task.ID, codersdk.TaskSendRequest{Input: taskInput}); err != nil {
return xerrors.Errorf("send input to task: %w", err)
}
+2 -3
View File
@@ -83,10 +83,9 @@ func (r *RootCmd) taskStatus() *serpent.Command {
}
ctx := i.Context()
exp := codersdk.NewExperimentalClient(client)
identifier := i.Args[0]
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return err
}
@@ -107,7 +106,7 @@ func (r *RootCmd) taskStatus() *serpent.Command {
// TODO: implement streaming updates instead of polling
lastStatusRow := tsr
for range t.C {
task, err := exp.TaskByID(ctx, task.ID)
task, err := client.TaskByID(ctx, task.ID)
if err != nil {
return err
}
+5 -5
View File
@@ -36,7 +36,7 @@ func Test_TaskStatus(t *testing.T) {
hf: func(ctx context.Context, _ time.Time) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/doesnotexist":
case "/api/v2/tasks/me/doesnotexist":
httpapi.ResourceNotFound(w)
return
default:
@@ -52,7 +52,7 @@ func Test_TaskStatus(t *testing.T) {
hf: func(ctx context.Context, now time.Time) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/exists":
case "/api/v2/tasks/me/exists":
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
WorkspaceStatus: codersdk.WorkspaceStatusRunning,
@@ -88,7 +88,7 @@ func Test_TaskStatus(t *testing.T) {
var calls atomic.Int64
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/exists":
case "/api/v2/tasks/me/exists":
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
Name: "exists",
@@ -103,7 +103,7 @@ func Test_TaskStatus(t *testing.T) {
Status: codersdk.TaskStatusPending,
})
return
case "/api/experimental/tasks/me/11111111-1111-1111-1111-111111111111":
case "/api/v2/tasks/me/11111111-1111-1111-1111-111111111111":
defer calls.Add(1)
switch calls.Load() {
case 0:
@@ -219,7 +219,7 @@ func Test_TaskStatus(t *testing.T) {
ts := time.Date(2025, 8, 26, 12, 34, 56, 0, time.UTC)
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/exists":
case "/api/v2/tasks/me/exists":
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
Name: "exists",
+2 -4
View File
@@ -122,8 +122,7 @@ func Test_Tasks(t *testing.T) {
assertFn: func(stdout string, userClient *codersdk.Client) {
// The task should eventually no longer show up in the list of tasks
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
expClient := codersdk.NewExperimentalClient(userClient)
tasks, err := expClient.Tasks(ctx, &codersdk.TasksFilter{})
tasks, err := userClient.Tasks(ctx, &codersdk.TasksFilter{})
if !assert.NoError(t, err) {
return false
}
@@ -248,8 +247,7 @@ func setupCLITaskTest(ctx context.Context, t *testing.T, agentAPIHandlers map[st
template := createAITaskTemplate(t, client, owner.OrganizationID, withSidebarURL(fakeAPI.URL()), withAgentToken(authToken))
wantPrompt := "test prompt"
exp := codersdk.NewExperimentalClient(userClient)
task, err := exp.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := userClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: wantPrompt,
Name: "test-task",
+25 -53
View File
@@ -31,17 +31,15 @@ import (
)
// @Summary Create a new AI task
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID create-task
// @ID create-a-new-ai-task
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Experimental
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param request body codersdk.CreateTaskRequest true "Create task request"
// @Success 201 {object} codersdk.Task
// @Router /api/experimental/tasks/{user} [post]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// This endpoint creates a new task for the given user.
// @Router /tasks/{user} [post]
func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -400,16 +398,13 @@ func deriveTaskCurrentState(
}
// @Summary List AI tasks
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID list-tasks
// @ID list-ai-tasks
// @Security CoderSessionToken
// @Produce json
// @Tags Experimental
// @Param q query string false "Search query for filtering tasks. Supports: owner:<username/uuid/me>, organization:<org-name/uuid>, status:<status>"
// @Success 200 {object} codersdk.TasksListResponse
// @Router /api/experimental/tasks [get]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// tasksList is an experimental endpoint to list tasks.
// @Router /tasks [get]
func (api *API) tasksList(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -502,20 +497,15 @@ func (api *API) convertTasks(ctx context.Context, requesterID uuid.UUID, dbTasks
return result, nil
}
// @Summary Get AI task by ID
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID get-task
// @Summary Get AI task by ID or name
// @ID get-ai-task-by-id-or-name
// @Security CoderSessionToken
// @Produce json
// @Tags Experimental
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID, or task name"
// @Success 200 {object} codersdk.Task
// @Router /api/experimental/tasks/{user}/{task} [get]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskGet is an experimental endpoint to fetch a single AI task by ID
// (workspace ID). It returns a synthesized task response including
// prompt and status.
// @Router /tasks/{user}/{task} [get]
func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -580,20 +570,14 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, taskResp)
}
// @Summary Delete AI task by ID
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID delete-task
// @Summary Delete AI task
// @ID delete-ai-task
// @Security CoderSessionToken
// @Tags Experimental
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID, or task name"
// @Success 202 "Task deletion initiated"
// @Router /api/experimental/tasks/{user}/{task} [delete]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskDelete is an experimental endpoint to delete a task by ID.
// It creates a delete workspace build and returns 202 Accepted if the build was
// created.
// @Success 202
// @Router /tasks/{user}/{task} [delete]
func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -655,18 +639,15 @@ func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
}
// @Summary Update AI task input
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID update-task-input
// @ID update-ai-task-input
// @Security CoderSessionToken
// @Accept json
// @Tags Experimental
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID, or task name"
// @Param request body codersdk.UpdateTaskInputRequest true "Update task input request"
// @Success 204
// @Router /api/experimental/tasks/{user}/{task}/input [patch]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskUpdateInput allows modifying a task's prompt before the agent executes it.
// @Router /tasks/{user}/{task}/input [patch]
func (api *API) taskUpdateInput(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -738,20 +719,15 @@ func (api *API) taskUpdateInput(rw http.ResponseWriter, r *http.Request) {
}
// @Summary Send input to AI task
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID send-task-input
// @ID send-input-to-ai-task
// @Security CoderSessionToken
// @Accept json
// @Tags Experimental
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID, or task name"
// @Param request body codersdk.TaskSendRequest true "Task input request"
// @Success 204 "Input sent successfully"
// @Router /api/experimental/tasks/{user}/{task}/send [post]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskSend submits task input to the task app by dialing the agent
// directly over the tailnet. We enforce ApplicationConnect RBAC on the
// workspace and validate the task app health.
// @Success 204
// @Router /tasks/{user}/{task}/send [post]
func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
task := httpmw.TaskParam(r)
@@ -812,18 +788,14 @@ func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
}
// @Summary Get AI task logs
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID get-task-logs
// @ID get-ai-task-logs
// @Security CoderSessionToken
// @Produce json
// @Tags Experimental
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID, or task name"
// @Success 200 {object} codersdk.TaskLogsResponse
// @Router /api/experimental/tasks/{user}/{task}/logs [get]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskLogs reads task output by dialing the agent directly over the tailnet.
// We enforce ApplicationConnect RBAC on the workspace and validate the task app health.
// @Router /tasks/{user}/{task}/logs [get]
func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
task := httpmw.TaskParam(r)
+56 -91
View File
@@ -124,8 +124,7 @@ func TestTasks(t *testing.T) {
// Create a task with a specific prompt using the new data model.
wantPrompt := "build me a web app"
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: wantPrompt,
})
@@ -141,7 +140,7 @@ func TestTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
// List tasks via experimental API and verify the prompt and status mapping.
tasks, err := exp.Tasks(ctx, &codersdk.TasksFilter{Owner: codersdk.Me})
tasks, err := client.Tasks(ctx, &codersdk.TasksFilter{Owner: codersdk.Me})
require.NoError(t, err)
got, ok := slice.Find(tasks, func(t codersdk.Task) bool { return t.ID == task.ID })
@@ -164,10 +163,9 @@ func TestTasks(t *testing.T) {
anotherUser, _ = coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
template = createAITemplate(t, client, user)
wantPrompt = "review my code"
exp = codersdk.NewExperimentalClient(client)
)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: wantPrompt,
})
@@ -201,7 +199,7 @@ func TestTasks(t *testing.T) {
require.NoError(t, err)
// Fetch the task by ID via experimental API and verify fields.
updated, err := exp.TaskByID(ctx, task.ID)
updated, err := client.TaskByID(ctx, task.ID)
require.NoError(t, err)
assert.Equal(t, task.ID, updated.ID, "task ID should match")
@@ -215,19 +213,18 @@ func TestTasks(t *testing.T) {
assert.NotEmpty(t, updated.WorkspaceStatus, "task status should not be empty")
// Fetch the task by name and verify the same result
byName, err := exp.TaskByOwnerAndName(ctx, codersdk.Me, task.Name)
byName, err := client.TaskByOwnerAndName(ctx, codersdk.Me, task.Name)
require.NoError(t, err)
require.Equal(t, byName, updated)
// Another member user should not be able to fetch the task
otherClient := codersdk.NewExperimentalClient(anotherUser)
_, err = otherClient.TaskByID(ctx, task.ID)
_, err = anotherUser.TaskByID(ctx, task.ID)
require.Error(t, err, "fetching task should fail by ID for another member user")
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
require.Equal(t, http.StatusNotFound, sdkErr.StatusCode())
// Also test by name
_, err = otherClient.TaskByOwnerAndName(ctx, task.OwnerName, task.Name)
_, err = anotherUser.TaskByOwnerAndName(ctx, task.OwnerName, task.Name)
require.Error(t, err, "fetching task should fail by name for another member user")
require.ErrorAs(t, err, &sdkErr)
require.Equal(t, http.StatusNotFound, sdkErr.StatusCode())
@@ -236,7 +233,7 @@ func TestTasks(t *testing.T) {
coderdtest.MustTransitionWorkspace(t, client, task.WorkspaceID.UUID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop)
// Verify that the previous status still remains
updated, err = exp.TaskByID(ctx, task.ID)
updated, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
assert.NotNil(t, updated.CurrentState, "current state should not be nil")
assert.Equal(t, "all done", updated.CurrentState.Message)
@@ -248,7 +245,7 @@ func TestTasks(t *testing.T) {
// Verify that the status from the previous build has been cleared
// and replaced by the agent initialization status.
updated, err = exp.TaskByID(ctx, task.ID)
updated, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
assert.NotEqual(t, previousCurrentState, updated.CurrentState)
assert.Equal(t, codersdk.TaskStateWorking, updated.CurrentState.State)
@@ -267,8 +264,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me",
})
@@ -281,7 +277,7 @@ func TestTasks(t *testing.T) {
}
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
err = exp.DeleteTask(ctx, "me", task.ID)
err = client.DeleteTask(ctx, "me", task.ID)
require.NoError(t, err, "delete task request should be accepted")
// Poll until the workspace is deleted.
@@ -303,8 +299,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitShort)
exp := codersdk.NewExperimentalClient(client)
err := exp.DeleteTask(ctx, "me", uuid.New())
err := client.DeleteTask(ctx, "me", uuid.New())
var sdkErr *codersdk.Error
require.Error(t, err, "expected an error for non-existent task")
@@ -330,8 +325,7 @@ func TestTasks(t *testing.T) {
}
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
exp := codersdk.NewExperimentalClient(client)
err := exp.DeleteTask(ctx, "me", ws.ID)
err := client.DeleteTask(ctx, "me", ws.ID)
var sdkErr *codersdk.Error
require.Error(t, err, "expected an error for non-task workspace delete via tasks endpoint")
@@ -350,8 +344,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitShort)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me not",
})
@@ -363,10 +356,9 @@ func TestTasks(t *testing.T) {
// Another regular org member without elevated permissions.
otherClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
expOther := codersdk.NewExperimentalClient(otherClient)
// Attempt to delete the owner's task as a non-owner without permissions.
err = expOther.DeleteTask(ctx, "me", task.ID)
err = otherClient.DeleteTask(ctx, "me", task.ID)
var authErr *codersdk.Error
require.Error(t, err, "expected an authorization error when deleting another user's task")
@@ -384,8 +376,7 @@ func TestTasks(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
template := createAITemplate(t, client, user)
ctx := testutil.Context(t, testutil.WaitLong)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me",
})
@@ -404,9 +395,9 @@ func TestTasks(t *testing.T) {
// Provisionerdserver will attempt delete the related task when deleting a workspace.
// This test ensures that we can still handle the case where, for some reason, the
// task has not been marked as deleted, but the workspace has.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err, "fetching a task should still work if its related workspace is deleted")
err = exp.DeleteTask(ctx, task.OwnerID.String(), task.ID)
err = client.DeleteTask(ctx, task.OwnerID.String(), task.ID)
require.NoError(t, err, "should be possible to delete a task with no workspace")
})
@@ -419,8 +410,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me",
})
@@ -436,7 +426,7 @@ func TestTasks(t *testing.T) {
// When; the task workspace is deleted
coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionDelete)
// Then: the task associated with the workspace is also deleted
_, err = exp.TaskByID(ctx, task.ID)
_, err = client.TaskByID(ctx, task.ID)
require.Error(t, err, "expected an error fetching the task")
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr, "expected a codersdk.Error")
@@ -495,10 +485,9 @@ func TestTasks(t *testing.T) {
userClient, _ = coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
agentAuthToken = uuid.NewString()
template = createAITemplate(t, client, owner, withAgentToken(agentAuthToken), withSidebarURL(srv.URL))
exp = codersdk.NewExperimentalClient(userClient)
)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := userClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "send me food",
})
@@ -511,7 +500,7 @@ func TestTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, ws.LatestBuild.ID)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.NotZero(t, task.WorkspaceBuildNumber)
require.True(t, task.WorkspaceAgentID.Valid)
@@ -537,7 +526,7 @@ func TestTasks(t *testing.T) {
coderdtest.NewWorkspaceAgentWaiter(t, userClient, ws.ID).WaitFor(coderdtest.AgentsReady)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
// Make the sidebar app unhealthy initially.
@@ -547,7 +536,7 @@ func TestTasks(t *testing.T) {
})
require.NoError(t, err)
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "Hello, Agent!",
})
require.Error(t, err, "wanted error due to unhealthy sidebar app")
@@ -561,7 +550,7 @@ func TestTasks(t *testing.T) {
statusResponse = agentapisdk.AgentStatus("bad")
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "Hello, Agent!",
})
require.Error(t, err, "wanted error due to bad status")
@@ -570,7 +559,7 @@ func TestTasks(t *testing.T) {
//nolint:tparallel // Not intended to run in parallel.
t.Run("SendOK", func(t *testing.T) {
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "Hello, Agent!",
})
require.NoError(t, err, "wanted no error due to healthy sidebar app and stable status")
@@ -578,7 +567,7 @@ func TestTasks(t *testing.T) {
//nolint:tparallel // Not intended to run in parallel.
t.Run("MissingContent", func(t *testing.T) {
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "",
})
require.Error(t, err, "wanted error due to missing content")
@@ -596,8 +585,7 @@ func TestTasks(t *testing.T) {
_ = coderdtest.CreateFirstUser(t, client)
ctx := testutil.Context(t, testutil.WaitShort)
exp := codersdk.NewExperimentalClient(client)
err := exp.TaskSend(ctx, "me", uuid.New(), codersdk.TaskSendRequest{
err := client.TaskSend(ctx, "me", uuid.New(), codersdk.TaskSendRequest{
Input: "hi",
})
@@ -663,10 +651,9 @@ func TestTasks(t *testing.T) {
owner = coderdtest.CreateFirstUser(t, client)
agentAuthToken = uuid.NewString()
template = createAITemplate(t, client, owner, withAgentToken(agentAuthToken), withSidebarURL(srv.URL))
exp = codersdk.NewExperimentalClient(client)
)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "show logs",
})
@@ -679,7 +666,7 @@ func TestTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByIdentifier(ctx, task.ID.String())
task, err = client.TaskByIdentifier(ctx, task.ID.String())
require.NoError(t, err)
require.NotZero(t, task.WorkspaceBuildNumber)
require.True(t, task.WorkspaceAgentID.Valid)
@@ -705,13 +692,13 @@ func TestTasks(t *testing.T) {
coderdtest.NewWorkspaceAgentWaiter(t, client, ws.ID).WaitFor(coderdtest.AgentsReady)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
//nolint:tparallel // Not intended to run in parallel.
t.Run("OK", func(t *testing.T) {
// Fetch logs.
resp, err := exp.TaskLogs(ctx, "me", task.ID)
resp, err := client.TaskLogs(ctx, "me", task.ID)
require.NoError(t, err)
require.Len(t, resp.Logs, 3)
assert.Equal(t, 0, resp.Logs[0].ID)
@@ -731,7 +718,7 @@ func TestTasks(t *testing.T) {
t.Run("UpstreamError", func(t *testing.T) {
shouldReturnError = true
t.Cleanup(func() { shouldReturnError = false })
_, err := exp.TaskLogs(ctx, "me", task.ID)
_, err := client.TaskLogs(ctx, "me", task.ID)
var sdkErr *codersdk.Error
require.Error(t, err)
@@ -811,8 +798,7 @@ func TestTasks(t *testing.T) {
}
// Given: We create a task
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "initial prompt",
})
@@ -844,17 +830,17 @@ func TestTasks(t *testing.T) {
}
if tt.deleteTask {
err = exp.DeleteTask(ctx, codersdk.Me, task.ID)
err = client.DeleteTask(ctx, codersdk.Me, task.ID)
require.NoError(t, err)
} else {
// Given: Task has expected status
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.Equal(t, tt.wantStatus, task.Status)
}
// When: We attempt to update the task input
err = exp.UpdateTaskInput(ctx, task.OwnerName, task.ID, codersdk.UpdateTaskInputRequest{
err = client.UpdateTaskInput(ctx, task.OwnerName, task.ID, codersdk.UpdateTaskInputRequest{
Input: tt.taskInput,
})
if tt.wantErr != "" {
@@ -868,7 +854,7 @@ func TestTasks(t *testing.T) {
if !tt.deleteTask {
// Then: We expect the input to **not** be updated
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.NotEqual(t, tt.taskInput, task.InitialPrompt)
}
@@ -877,7 +863,7 @@ func TestTasks(t *testing.T) {
if !tt.deleteTask {
// Then: We expect the input to be updated
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.Equal(t, tt.taskInput, task.InitialPrompt)
}
@@ -892,10 +878,8 @@ func TestTasks(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
ctx := testutil.Context(t, testutil.WaitShort)
exp := codersdk.NewExperimentalClient(client)
// Attempt to update prompt for non-existent task
err := exp.UpdateTaskInput(ctx, user.UserID.String(), uuid.New(), codersdk.UpdateTaskInputRequest{
err := client.UpdateTaskInput(ctx, user.UserID.String(), uuid.New(), codersdk.UpdateTaskInputRequest{
Input: "Should fail",
})
require.Error(t, err)
@@ -915,8 +899,7 @@ func TestTasks(t *testing.T) {
template := createAITemplate(t, client, user)
// Create a task as the first user
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "initial prompt",
})
@@ -933,8 +916,7 @@ func TestTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, build.ID)
// Attempt to update prompt as another user should fail with 404 Not Found
otherExp := codersdk.NewExperimentalClient(anotherUser)
err = otherExp.UpdateTaskInput(ctx, task.OwnerName, task.ID, codersdk.UpdateTaskInputRequest{
err = anotherUser.UpdateTaskInput(ctx, task.OwnerName, task.ID, codersdk.UpdateTaskInputRequest{
Input: "Should fail - unauthorized",
})
require.Error(t, err)
@@ -972,9 +954,7 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -1019,10 +999,8 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
// When: We attempt to create a Task.
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -1107,11 +1085,10 @@ func TestTasksCreate(t *testing.T) {
t.Parallel()
var (
ctx = testutil.Context(t, testutil.WaitShort)
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
expClient = codersdk.NewExperimentalClient(client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
ctx = testutil.Context(t, testutil.WaitShort)
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: echo.ApplyComplete,
ProvisionPlan: []*proto.Response{
@@ -1126,7 +1103,7 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
// When: We attempt to create a Task.
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "Some prompt",
Name: tt.taskName,
@@ -1177,10 +1154,8 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
// When: We attempt to create a Task.
_, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
_, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -1209,10 +1184,8 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
// When: We attempt to create a Task with an invalid template version ID.
_, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
_, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: uuid.New(),
Input: taskPrompt,
})
@@ -1248,9 +1221,7 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -1307,9 +1278,7 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
Name: taskName,
@@ -1343,16 +1312,14 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task1, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task1, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "First task",
Name: "task-1",
})
require.NoError(t, err)
task2, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task2, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "Second task",
Name: "task-2",
@@ -1406,11 +1373,9 @@ func TestTasksCreate(t *testing.T) {
}, template.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID)
expClient := codersdk.NewExperimentalClient(client)
// Create a task using version 2 to verify the template_version_id is
// stored correctly.
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: version2.ID,
Input: "Use version 2",
})
+288 -267
View File
@@ -136,273 +136,6 @@ const docTemplate = `{
}
}
},
"/api/experimental/tasks": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "List AI tasks",
"operationId": "list-tasks",
"parameters": [
{
"type": "string",
"description": "Search query for filtering tasks. Supports: owner:\u003cusername/uuid/me\u003e, organization:\u003corg-name/uuid\u003e, status:\u003cstatus\u003e",
"name": "q",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TasksListResponse"
}
}
}
}
},
"/api/experimental/tasks/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Create a new AI task",
"operationId": "create-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"description": "Create task request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateTaskRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
}
},
"/api/experimental/tasks/{user}/{task}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Get AI task by ID",
"operationId": "get-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Delete AI task by ID",
"operationId": "delete-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Task deletion initiated"
}
}
}
},
"/api/experimental/tasks/{user}/{task}/input": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Update AI task input",
"operationId": "update-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Update task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.UpdateTaskInputRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/api/experimental/tasks/{user}/{task}/logs": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Get AI task logs",
"operationId": "get-task-logs",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TaskLogsResponse"
}
}
}
}
},
"/api/experimental/tasks/{user}/{task}/send": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Send input to AI task",
"operationId": "send-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.TaskSendRequest"
}
}
],
"responses": {
"204": {
"description": "Input sent successfully"
}
}
}
},
"/appearance": {
"get": {
"security": [
@@ -5719,6 +5452,294 @@ const docTemplate = `{
}
}
},
"/tasks": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Experimental"
],
"summary": "List AI tasks",
"operationId": "list-ai-tasks",
"parameters": [
{
"type": "string",
"description": "Search query for filtering tasks. Supports: owner:\u003cusername/uuid/me\u003e, organization:\u003corg-name/uuid\u003e, status:\u003cstatus\u003e",
"name": "q",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TasksListResponse"
}
}
}
}
},
"/tasks/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Experimental"
],
"summary": "Create a new AI task",
"operationId": "create-a-new-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"description": "Create task request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateTaskRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
}
},
"/tasks/{user}/{task}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Experimental"
],
"summary": "Get AI task by ID or name",
"operationId": "get-ai-task-by-id-or-name",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Delete AI task",
"operationId": "delete-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Accepted"
}
}
}
},
"/tasks/{user}/{task}/input": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"tags": [
"Experimental"
],
"summary": "Update AI task input",
"operationId": "update-ai-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Update task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.UpdateTaskInputRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/tasks/{user}/{task}/logs": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Experimental"
],
"summary": "Get AI task logs",
"operationId": "get-ai-task-logs",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TaskLogsResponse"
}
}
}
}
},
"/tasks/{user}/{task}/send": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"tags": [
"Experimental"
],
"summary": "Send input to AI task",
"operationId": "send-input-to-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.TaskSendRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/templates": {
"get": {
"security": [
+260 -253
View File
@@ -112,259 +112,6 @@
}
}
},
"/api/experimental/tasks": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "List AI tasks",
"operationId": "list-tasks",
"parameters": [
{
"type": "string",
"description": "Search query for filtering tasks. Supports: owner:\u003cusername/uuid/me\u003e, organization:\u003corg-name/uuid\u003e, status:\u003cstatus\u003e",
"name": "q",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TasksListResponse"
}
}
}
}
},
"/api/experimental/tasks/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "Create a new AI task",
"operationId": "create-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"description": "Create task request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateTaskRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
}
},
"/api/experimental/tasks/{user}/{task}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "Get AI task by ID",
"operationId": "get-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "Delete AI task by ID",
"operationId": "delete-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Task deletion initiated"
}
}
}
},
"/api/experimental/tasks/{user}/{task}/input": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "Update AI task input",
"operationId": "update-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Update task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.UpdateTaskInputRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/api/experimental/tasks/{user}/{task}/logs": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "Get AI task logs",
"operationId": "get-task-logs",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TaskLogsResponse"
}
}
}
}
},
"/api/experimental/tasks/{user}/{task}/send": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "Send input to AI task",
"operationId": "send-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.TaskSendRequest"
}
}
],
"responses": {
"204": {
"description": "Input sent successfully"
}
}
}
},
"/appearance": {
"get": {
"security": [
@@ -5064,6 +4811,266 @@
}
}
},
"/tasks": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Experimental"],
"summary": "List AI tasks",
"operationId": "list-ai-tasks",
"parameters": [
{
"type": "string",
"description": "Search query for filtering tasks. Supports: owner:\u003cusername/uuid/me\u003e, organization:\u003corg-name/uuid\u003e, status:\u003cstatus\u003e",
"name": "q",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TasksListResponse"
}
}
}
}
},
"/tasks/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Experimental"],
"summary": "Create a new AI task",
"operationId": "create-a-new-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"description": "Create task request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateTaskRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
}
},
"/tasks/{user}/{task}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Experimental"],
"summary": "Get AI task by ID or name",
"operationId": "get-ai-task-by-id-or-name",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Experimental"],
"summary": "Delete AI task",
"operationId": "delete-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Accepted"
}
}
}
},
"/tasks/{user}/{task}/input": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"tags": ["Experimental"],
"summary": "Update AI task input",
"operationId": "update-ai-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Update task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.UpdateTaskInputRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/tasks/{user}/{task}/logs": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Experimental"],
"summary": "Get AI task logs",
"operationId": "get-ai-task-logs",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TaskLogsResponse"
}
}
}
}
},
"/tasks/{user}/{task}/send": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"tags": ["Experimental"],
"summary": "Send input to AI task",
"operationId": "send-input-to-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.TaskSendRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/templates": {
"get": {
"security": [
+1 -2
View File
@@ -1830,8 +1830,7 @@ func TestExecutorTaskWorkspace(t *testing.T) {
createTaskWorkspace := func(t *testing.T, client *codersdk.Client, template codersdk.Template, ctx context.Context, input string) codersdk.Workspace {
t.Helper()
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: input,
})
+22
View File
@@ -1023,6 +1023,9 @@ func New(options *Options) *API {
httpmw.ReportCLITelemetry(api.Logger, options.Telemetry),
)
// NOTE(DanielleMaywood):
// Tasks have been promoted to stable, but we have guaranteed a single release transition period
// where these routes must remain. These should be removed no earlier than Coder v2.30.0
r.Route("/tasks", func(r chi.Router) {
r.Use(apiKeyMiddleware)
@@ -1650,6 +1653,25 @@ func New(options *Options) *API {
r.Route("/init-script", func(r chi.Router) {
r.Get("/{os}/{arch}", api.initScript)
})
r.Route("/tasks", func(r chi.Router) {
r.Use(apiKeyMiddleware)
r.Get("/", api.tasksList)
r.Route("/{user}", func(r chi.Router) {
r.Use(httpmw.ExtractOrganizationMembersParam(options.Database, api.HTTPAuth.Authorize))
r.Post("/", api.tasksCreate)
r.Route("/{task}", func(r chi.Router) {
r.Use(httpmw.ExtractTaskParam(options.Database))
r.Get("/", api.taskGet)
r.Delete("/", api.taskDelete)
r.Patch("/input", api.taskUpdateInput)
r.Post("/send", api.taskSend)
r.Get("/logs", api.taskLogs)
})
})
})
})
if options.SwaggerEndpoint {
+1 -2
View File
@@ -4800,7 +4800,6 @@ func TestWorkspaceListTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitShort)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
@@ -4823,7 +4822,7 @@ func TestWorkspaceListTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceWithoutTask.LatestBuild.ID)
// Given: a workspace associated with a task
task, err := expClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "Some task prompt",
})
+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,
+185 -26
View File
@@ -6,12 +6,12 @@
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/api/experimental/tasks \
-H 'Accept: */*' \
curl -X GET http://coder-server:8080/api/v2/tasks \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /api/experimental/tasks`
`GET /tasks`
### Parameters
@@ -23,6 +23,58 @@ curl -X GET http://coder-server:8080/api/v2/api/experimental/tasks \
> 200 Response
```json
{
"count": 0,
"tasks": [
{
"created_at": "2019-08-24T14:15:22Z",
"current_state": {
"message": "string",
"state": "working",
"timestamp": "2019-08-24T14:15:22Z",
"uri": "string"
},
"display_name": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initial_prompt": "string",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_agent_health": {
"healthy": false,
"reason": "agent has lost connection"
},
"workspace_agent_id": {
"uuid": "string",
"valid": true
},
"workspace_agent_lifecycle": "created",
"workspace_app_id": {
"uuid": "string",
"valid": true
},
"workspace_build_number": 0,
"workspace_id": {
"uuid": "string",
"valid": true
},
"workspace_name": "string",
"workspace_status": "pending"
}
]
}
```
### Responses
| Status | Meaning | Description | Schema |
@@ -37,13 +89,13 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/api/experimental/tasks/{user} \
curl -X POST http://coder-server:8080/api/v2/tasks/{user} \
-H 'Content-Type: application/json' \
-H 'Accept: */*' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /api/experimental/tasks/{user}`
`POST /tasks/{user}`
> Body parameter
@@ -68,6 +120,53 @@ curl -X POST http://coder-server:8080/api/v2/api/experimental/tasks/{user} \
> 201 Response
```json
{
"created_at": "2019-08-24T14:15:22Z",
"current_state": {
"message": "string",
"state": "working",
"timestamp": "2019-08-24T14:15:22Z",
"uri": "string"
},
"display_name": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initial_prompt": "string",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_agent_health": {
"healthy": false,
"reason": "agent has lost connection"
},
"workspace_agent_id": {
"uuid": "string",
"valid": true
},
"workspace_agent_lifecycle": "created",
"workspace_app_id": {
"uuid": "string",
"valid": true
},
"workspace_build_number": 0,
"workspace_id": {
"uuid": "string",
"valid": true
},
"workspace_name": "string",
"workspace_status": "pending"
}
```
### Responses
| Status | Meaning | Description | Schema |
@@ -76,18 +175,18 @@ curl -X POST http://coder-server:8080/api/v2/api/experimental/tasks/{user} \
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get AI task by ID
## Get AI task by ID or name
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task} \
-H 'Accept: */*' \
curl -X GET http://coder-server:8080/api/v2/tasks/{user}/{task} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /api/experimental/tasks/{user}/{task}`
`GET /tasks/{user}/{task}`
### Parameters
@@ -100,6 +199,53 @@ curl -X GET http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task}
> 200 Response
```json
{
"created_at": "2019-08-24T14:15:22Z",
"current_state": {
"message": "string",
"state": "working",
"timestamp": "2019-08-24T14:15:22Z",
"uri": "string"
},
"display_name": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initial_prompt": "string",
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"updated_at": "2019-08-24T14:15:22Z",
"workspace_agent_health": {
"healthy": false,
"reason": "agent has lost connection"
},
"workspace_agent_id": {
"uuid": "string",
"valid": true
},
"workspace_agent_lifecycle": "created",
"workspace_app_id": {
"uuid": "string",
"valid": true
},
"workspace_build_number": 0,
"workspace_id": {
"uuid": "string",
"valid": true
},
"workspace_name": "string",
"workspace_status": "pending"
}
```
### Responses
| Status | Meaning | Description | Schema |
@@ -108,17 +254,17 @@ curl -X GET http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task}
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Delete AI task by ID
## Delete AI task
### Code samples
```shell
# Example request using curl
curl -X DELETE http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task} \
curl -X DELETE http://coder-server:8080/api/v2/tasks/{user}/{task} \
-H 'Coder-Session-Token: API_KEY'
```
`DELETE /api/experimental/tasks/{user}/{task}`
`DELETE /tasks/{user}/{task}`
### Parameters
@@ -129,9 +275,9 @@ curl -X DELETE http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{ta
### Responses
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------------|-------------------------|--------|
| 202 | [Accepted](https://tools.ietf.org/html/rfc7231#section-6.3.3) | Task deletion initiated | |
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------------|-------------|--------|
| 202 | [Accepted](https://tools.ietf.org/html/rfc7231#section-6.3.3) | Accepted | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
@@ -141,12 +287,12 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task}/input \
curl -X PATCH http://coder-server:8080/api/v2/tasks/{user}/{task}/input \
-H 'Content-Type: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /api/experimental/tasks/{user}/{task}/input`
`PATCH /tasks/{user}/{task}/input`
> Body parameter
@@ -178,12 +324,12 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task}/logs \
-H 'Accept: */*' \
curl -X GET http://coder-server:8080/api/v2/tasks/{user}/{task}/logs \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /api/experimental/tasks/{user}/{task}/logs`
`GET /tasks/{user}/{task}/logs`
### Parameters
@@ -196,6 +342,19 @@ curl -X GET http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task}
> 200 Response
```json
{
"logs": [
{
"content": "string",
"id": 0,
"time": "2019-08-24T14:15:22Z",
"type": "input"
}
]
}
```
### Responses
| Status | Meaning | Description | Schema |
@@ -210,12 +369,12 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task}/send \
curl -X POST http://coder-server:8080/api/v2/tasks/{user}/{task}/send \
-H 'Content-Type: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`POST /api/experimental/tasks/{user}/{task}/send`
`POST /tasks/{user}/{task}/send`
> Body parameter
@@ -235,8 +394,8 @@ curl -X POST http://coder-server:8080/api/v2/api/experimental/tasks/{user}/{task
### Responses
| Status | Meaning | Description | Schema |
|--------|-----------------------------------------------------------------|-------------------------|--------|
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | Input sent successfully | |
| Status | Meaning | Description | Schema |
|--------|-----------------------------------------------------------------|-------------|--------|
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
+4 -4
View File
@@ -2666,7 +2666,7 @@ class ExperimentalApiMethods {
req: TypesGen.CreateTaskRequest,
): Promise<TypesGen.Task> => {
const response = await this.axios.post<TypesGen.Task>(
`/api/experimental/tasks/${user}`,
`/api/v2/tasks/${user}`,
req,
);
@@ -2685,7 +2685,7 @@ class ExperimentalApiMethods {
}
const res = await this.axios.get<TypesGen.TasksListResponse>(
"/api/experimental/tasks",
"/api/v2/tasks",
{
params: {
q: query.join(", "),
@@ -2698,14 +2698,14 @@ class ExperimentalApiMethods {
getTask = async (user: string, id: string): Promise<TypesGen.Task> => {
const response = await this.axios.get<TypesGen.Task>(
`/api/experimental/tasks/${user}/${id}`,
`/api/v2/tasks/${user}/${id}`,
);
return response.data;
};
deleteTask = async (user: string, id: string): Promise<void> => {
await this.axios.delete(`/api/experimental/tasks/${user}/${id}`);
await this.axios.delete(`/api/v2/tasks/${user}/${id}`);
};
createTaskFeedback = async (
-18
View File
@@ -1195,8 +1195,6 @@ export interface CreateProvisionerKeyResponse {
// From codersdk/aitasks.go
/**
* CreateTaskRequest represents the request to create a new task.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface CreateTaskRequest {
readonly template_version_id: string;
@@ -4723,8 +4721,6 @@ export interface TailDERPRegion {
// From codersdk/aitasks.go
/**
* Task represents a task.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface Task {
readonly id: string;
@@ -4757,8 +4753,6 @@ export interface Task {
// From codersdk/aitasks.go
/**
* TaskLogEntry represents a single log entry for a task.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface TaskLogEntry {
readonly id: number;
@@ -4775,8 +4769,6 @@ export const TaskLogTypes: TaskLogType[] = ["input", "output"];
// From codersdk/aitasks.go
/**
* TaskLogsResponse contains the logs for a task.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface TaskLogsResponse {
readonly logs: readonly TaskLogEntry[];
@@ -4785,8 +4777,6 @@ export interface TaskLogsResponse {
// From codersdk/aitasks.go
/**
* TaskSendRequest is used to send task input to the tasks sidebar app.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface TaskSendRequest {
readonly input: string;
@@ -4798,8 +4788,6 @@ export type TaskState = "complete" | "failed" | "idle" | "working";
// From codersdk/aitasks.go
/**
* TaskStateEntry represents a single entry in the task's state history.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface TaskStateEntry {
readonly timestamp: string;
@@ -4836,8 +4824,6 @@ export const TaskStatuses: TaskStatus[] = [
// From codersdk/aitasks.go
/**
* TasksFilter filters the list of tasks.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface TasksFilter {
/**
@@ -4861,8 +4847,6 @@ export interface TasksFilter {
// From codersdk/aitasks.go
/**
* TaskListResponse is the response shape for tasks list.
*
* Experimental response shape for tasks list (server returns []Task).
*/
export interface TasksListResponse {
readonly tasks: readonly Task[];
@@ -5376,8 +5360,6 @@ export interface UpdateRoles {
// From codersdk/aitasks.go
/**
* UpdateTaskInputRequest is used to update a task's input.
*
* Experimental: This type is experimental and may change in the future.
*/
export interface UpdateTaskInputRequest {
readonly input: string;