feat!: hide Coder Tasks behind the enable-ai-tasks flag (#28008)

This commit is contained in:
david-fraley
2026-08-19 14:46:10 -05:00
committed by GitHub
parent a3eb1ee930
commit 8203b2ebaf
85 changed files with 307 additions and 6030 deletions
+30 -93
View File
@@ -34,16 +34,9 @@ import (
"github.com/coder/coder/v2/codersdk"
)
// @Summary Create a new AI task
// @ID create-a-new-ai-task
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Tasks
// @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/v2/tasks/{user} [post]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -421,14 +414,9 @@ func deriveTaskCurrentState(
return currentState
}
// @Summary List AI tasks
// @ID list-ai-tasks
// @Security CoderSessionToken
// @Produce json
// @Tags Tasks
// @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/v2/tasks [get]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) tasksList(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -530,15 +518,9 @@ func (api *API) convertTasks(ctx context.Context, requesterID uuid.UUID, dbTasks
return result, nil
}
// @Summary Get AI task by ID or name
// @ID get-ai-task-by-id-or-name
// @Security CoderSessionToken
// @Produce json
// @Tags Tasks
// @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/v2/tasks/{user}/{task} [get]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -605,14 +587,9 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, taskResp)
}
// @Summary Delete AI task
// @ID delete-ai-task
// @Security CoderSessionToken
// @Tags Tasks
// @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
// @Router /api/v2/tasks/{user}/{task} [delete]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -677,16 +654,9 @@ func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusAccepted)
}
// @Summary Update AI task input
// @ID update-ai-task-input
// @Security CoderSessionToken
// @Accept json
// @Tags Tasks
// @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/v2/tasks/{user}/{task}/input [patch]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) taskUpdateInput(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -757,16 +727,9 @@ func (api *API) taskUpdateInput(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
}
// @Summary Send input to AI task
// @ID send-input-to-ai-task
// @Security CoderSessionToken
// @Accept json
// @Tags Tasks
// @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
// @Router /api/v2/tasks/{user}/{task}/send [post]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
task := httpmw.TaskParam(r)
@@ -850,15 +813,9 @@ func convertAgentAPIMessagesToLogEntries(messages []agentapisdk.Message) ([]code
return logs, nil
}
// @Summary Get AI task logs
// @ID get-ai-task-logs
// @Security CoderSessionToken
// @Produce json
// @Tags Tasks
// @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/v2/tasks/{user}/{task}/logs [get]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
task := httpmw.TaskParam(r)
@@ -1129,17 +1086,9 @@ type TaskLogSnapshotEnvelope struct {
Data any `json:"data"`
}
// @Summary Upload task log snapshot
// @ID upload-task-log-snapshot
// @Security CoderSessionToken
// @Accept json
// @Tags Tasks
// @Param task path string true "Task ID" format(uuid)
// @Param format query string true "Snapshot format" enums(agentapi)
// @Param request body object true "Raw snapshot payload (structure depends on format parameter)"
// @Success 204
// @Failure 413 {object} codersdk.Response "Request body exceeds 64 KiB"
// @Router /api/v2/workspaceagents/me/tasks/{task}/log-snapshot [post]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) postWorkspaceAgentTaskLogSnapshot(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -1275,15 +1224,9 @@ func (api *API) postWorkspaceAgentTaskLogSnapshot(rw http.ResponseWriter, r *htt
rw.WriteHeader(http.StatusNoContent)
}
// @Summary Pause task
// @ID pause-task
// @Security CoderSessionToken
// @Produce json
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID" format(uuid)
// @Success 202 {object} codersdk.PauseTaskResponse
// @Router /api/v2/tasks/{user}/{task}/pause [post]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) pauseTask(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -1352,15 +1295,9 @@ func (api *API) pauseTask(rw http.ResponseWriter, r *http.Request) {
})
}
// @Summary Resume task
// @ID resume-task
// @Security CoderSessionToken
// @Produce json
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID" format(uuid)
// @Success 202 {object} codersdk.ResumeTaskResponse
// @Router /api/v2/tasks/{user}/{task}/resume [post]
// Deprecated: Coder Tasks is deprecated as of v2.36. This route is only
// registered when CODER_ENABLE_AI_TASKS is set, and is planned for removal in
// v2.37.
func (api *API) resumeTask(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
+48
View File
@@ -0,0 +1,48 @@
package coderd_test
import (
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/testutil"
)
// TestTasksDisabled asserts that a deployment without CODER_ENABLE_AI_TASKS
// serves no Tasks routes, on either the stable or the experimental prefix.
func TestTasksDisabled(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := coderdtest.DeploymentValues(t)
values.EnableAITasks = false
client := coderdtest.New(t, &coderdtest.Options{DeploymentValues: values})
coderdtest.CreateFirstUser(t, client)
// Only the user-facing routes are asserted here. The agent-side
// /workspaceagents/me/tasks route is also gated, but it sits behind agent
// authentication, so a user token cannot tell a missing route from a
// rejected one. TestEndpointsDocumented covers its absence, since an
// undocumented registered route fails that test.
for _, route := range []string{
"/api/v2/tasks",
"/api/v2/tasks/me",
"/api/experimental/tasks",
"/api/experimental/tasks/me",
} {
res, err := client.Request(ctx, http.MethodGet, route, nil)
require.NoError(t, err)
_ = res.Body.Close()
require.Equal(t, http.StatusNotFound, res.StatusCode, "route %s should not be registered", route)
}
// Sanity check that unrelated routes still work, so the assertions above
// are not passing because the whole API is broken.
res, err := client.Request(ctx, http.MethodGet, "/api/v2/workspaces", nil)
require.NoError(t, err)
_ = res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
}
+3 -728
View File
@@ -8130,378 +8130,6 @@ const docTemplate = `{
]
}
},
"/api/v2/tasks": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
},
"delete": {
"tags": [
"Tasks"
],
"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"
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/input": {
"patch": {
"consumes": [
"application/json"
],
"tags": [
"Tasks"
],
"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"
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/logs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/pause": {
"post": {
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Pause task",
"operationId": "pause-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/codersdk.PauseTaskResponse"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/resume": {
"post": {
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Resume task",
"operationId": "resume-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/codersdk.ResumeTaskResponse"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/send": {
"post": {
"consumes": [
"application/json"
],
"tags": [
"Tasks"
],
"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"
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/templatebuilder/bases": {
"get": {
"produces": [
@@ -12954,63 +12582,6 @@ const docTemplate = `{
}
}
},
"/api/v2/workspaceagents/me/tasks/{task}/log-snapshot": {
"post": {
"consumes": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Upload task log snapshot",
"operationId": "upload-task-log-snapshot",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
},
{
"enum": [
"agentapi"
],
"type": "string",
"description": "Snapshot format",
"name": "format",
"in": "query",
"required": true
},
{
"description": "Raw snapshot payload (structure depends on format parameter)",
"name": "request",
"in": "body",
"required": true,
"schema": {
"type": "object"
}
}
],
"responses": {
"204": {
"description": "No Content"
},
"413": {
"description": "Request body exceeds 64 KiB",
"schema": {
"$ref": "#/definitions/codersdk.Response"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/workspaceagents/{workspaceagent}": {
"get": {
"produces": [
@@ -19956,28 +19527,6 @@ const docTemplate = `{
}
}
},
"codersdk.CreateTaskRequest": {
"type": "object",
"properties": {
"display_name": {
"type": "string"
},
"input": {
"type": "string"
},
"name": {
"type": "string"
},
"template_version_id": {
"type": "string",
"format": "uuid"
},
"template_version_preset_id": {
"type": "string",
"format": "uuid"
}
}
},
"codersdk.CreateTemplateRequest": {
"type": "object",
"required": [
@@ -20906,6 +20455,9 @@ const docTemplate = `{
"docs_url": {
"$ref": "#/definitions/serpent.URL"
},
"enable_ai_tasks": {
"type": "boolean"
},
"enable_authz_recording": {
"type": "boolean"
},
@@ -20936,9 +20488,6 @@ const docTemplate = `{
"healthcheck": {
"$ref": "#/definitions/codersdk.HealthcheckConfig"
},
"hide_ai_tasks": {
"type": "boolean"
},
"http_address": {
"description": "HTTPAddress is a string because it may be set to zero to disable.",
"type": "string"
@@ -23994,14 +23543,6 @@ const docTemplate = `{
}
}
},
"codersdk.PauseTaskResponse": {
"type": "object",
"properties": {
"workspace_build": {
"$ref": "#/definitions/codersdk.WorkspaceBuild"
}
}
},
"codersdk.Permission": {
"type": "object",
"properties": {
@@ -25219,14 +24760,6 @@ const docTemplate = `{
}
}
},
"codersdk.ResumeTaskResponse": {
"type": "object",
"properties": {
"workspace_build": {
"$ref": "#/definitions/codersdk.WorkspaceBuild"
}
}
},
"codersdk.RetentionConfig": {
"type": "object",
"properties": {
@@ -25570,256 +25103,6 @@ const docTemplate = `{
}
}
},
"codersdk.Task": {
"type": "object",
"properties": {
"created_at": {
"type": "string",
"format": "date-time"
},
"current_state": {
"$ref": "#/definitions/codersdk.TaskStateEntry"
},
"display_name": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"initial_prompt": {
"type": "string"
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string",
"format": "uuid"
},
"owner_avatar_url": {
"type": "string"
},
"owner_id": {
"type": "string",
"format": "uuid"
},
"owner_name": {
"type": "string"
},
"status": {
"enum": [
"pending",
"initializing",
"active",
"paused",
"unknown",
"error"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.TaskStatus"
}
]
},
"template_display_name": {
"type": "string"
},
"template_icon": {
"type": "string"
},
"template_id": {
"type": "string",
"format": "uuid"
},
"template_name": {
"type": "string"
},
"template_version_id": {
"type": "string",
"format": "uuid"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"workspace_agent_health": {
"$ref": "#/definitions/codersdk.WorkspaceAgentHealth"
},
"workspace_agent_id": {
"format": "uuid",
"allOf": [
{
"$ref": "#/definitions/uuid.NullUUID"
}
]
},
"workspace_agent_lifecycle": {
"$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle"
},
"workspace_app_id": {
"format": "uuid",
"allOf": [
{
"$ref": "#/definitions/uuid.NullUUID"
}
]
},
"workspace_build_number": {
"type": "integer"
},
"workspace_id": {
"format": "uuid",
"allOf": [
{
"$ref": "#/definitions/uuid.NullUUID"
}
]
},
"workspace_name": {
"type": "string"
},
"workspace_status": {
"enum": [
"pending",
"starting",
"running",
"stopping",
"stopped",
"failed",
"canceling",
"canceled",
"deleting",
"deleted"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.WorkspaceStatus"
}
]
}
}
},
"codersdk.TaskLogEntry": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"id": {
"type": "integer"
},
"time": {
"type": "string",
"format": "date-time"
},
"type": {
"$ref": "#/definitions/codersdk.TaskLogType"
}
}
},
"codersdk.TaskLogType": {
"type": "string",
"enum": [
"input",
"output"
],
"x-enum-varnames": [
"TaskLogTypeInput",
"TaskLogTypeOutput"
]
},
"codersdk.TaskLogsResponse": {
"type": "object",
"properties": {
"logs": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.TaskLogEntry"
}
},
"snapshot": {
"type": "boolean"
},
"snapshot_at": {
"type": "string"
}
}
},
"codersdk.TaskSendRequest": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
}
},
"codersdk.TaskState": {
"type": "string",
"enum": [
"working",
"idle",
"complete",
"failed"
],
"x-enum-varnames": [
"TaskStateWorking",
"TaskStateIdle",
"TaskStateComplete",
"TaskStateFailed"
]
},
"codersdk.TaskStateEntry": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"state": {
"$ref": "#/definitions/codersdk.TaskState"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"uri": {
"type": "string"
}
}
},
"codersdk.TaskStatus": {
"type": "string",
"enum": [
"pending",
"initializing",
"active",
"paused",
"unknown",
"error"
],
"x-enum-varnames": [
"TaskStatusPending",
"TaskStatusInitializing",
"TaskStatusActive",
"TaskStatusPaused",
"TaskStatusUnknown",
"TaskStatusError"
]
},
"codersdk.TasksListResponse": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"codersdk.TelemetryConfig": {
"type": "object",
"properties": {
@@ -27315,14 +26598,6 @@ const docTemplate = `{
}
}
},
"codersdk.UpdateTaskInputRequest": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
}
},
"codersdk.UpdateTemplateACL": {
"type": "object",
"properties": {
+3 -675
View File
@@ -7233,342 +7233,6 @@
]
}
},
"/api/v2/tasks": {
"get": {
"produces": ["application/json"],
"tags": ["Tasks"],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}": {
"post": {
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Tasks"],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}": {
"get": {
"produces": ["application/json"],
"tags": ["Tasks"],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
},
"delete": {
"tags": ["Tasks"],
"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"
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/input": {
"patch": {
"consumes": ["application/json"],
"tags": ["Tasks"],
"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"
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/logs": {
"get": {
"produces": ["application/json"],
"tags": ["Tasks"],
"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"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/pause": {
"post": {
"produces": ["application/json"],
"tags": ["Tasks"],
"summary": "Pause task",
"operationId": "pause-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/codersdk.PauseTaskResponse"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/resume": {
"post": {
"produces": ["application/json"],
"tags": ["Tasks"],
"summary": "Resume task",
"operationId": "resume-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/codersdk.ResumeTaskResponse"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/tasks/{user}/{task}/send": {
"post": {
"consumes": ["application/json"],
"tags": ["Tasks"],
"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"
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/templatebuilder/bases": {
"get": {
"produces": ["application/json"],
@@ -11511,57 +11175,6 @@
}
}
},
"/api/v2/workspaceagents/me/tasks/{task}/log-snapshot": {
"post": {
"consumes": ["application/json"],
"tags": ["Tasks"],
"summary": "Upload task log snapshot",
"operationId": "upload-task-log-snapshot",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
},
{
"enum": ["agentapi"],
"type": "string",
"description": "Snapshot format",
"name": "format",
"in": "query",
"required": true
},
{
"description": "Raw snapshot payload (structure depends on format parameter)",
"name": "request",
"in": "body",
"required": true,
"schema": {
"type": "object"
}
}
],
"responses": {
"204": {
"description": "No Content"
},
"413": {
"description": "Request body exceeds 64 KiB",
"schema": {
"$ref": "#/definitions/codersdk.Response"
}
}
},
"security": [
{
"CoderSessionToken": []
}
]
}
},
"/api/v2/workspaceagents/{workspaceagent}": {
"get": {
"produces": ["application/json"],
@@ -18065,28 +17678,6 @@
}
}
},
"codersdk.CreateTaskRequest": {
"type": "object",
"properties": {
"display_name": {
"type": "string"
},
"input": {
"type": "string"
},
"name": {
"type": "string"
},
"template_version_id": {
"type": "string",
"format": "uuid"
},
"template_version_preset_id": {
"type": "string",
"format": "uuid"
}
}
},
"codersdk.CreateTemplateRequest": {
"type": "object",
"required": ["name", "template_version_id"],
@@ -18976,6 +18567,9 @@
"docs_url": {
"$ref": "#/definitions/serpent.URL"
},
"enable_ai_tasks": {
"type": "boolean"
},
"enable_authz_recording": {
"type": "boolean"
},
@@ -19006,9 +18600,6 @@
"healthcheck": {
"$ref": "#/definitions/codersdk.HealthcheckConfig"
},
"hide_ai_tasks": {
"type": "boolean"
},
"http_address": {
"description": "HTTPAddress is a string because it may be set to zero to disable.",
"type": "string"
@@ -21961,14 +21552,6 @@
}
}
},
"codersdk.PauseTaskResponse": {
"type": "object",
"properties": {
"workspace_build": {
"$ref": "#/definitions/codersdk.WorkspaceBuild"
}
}
},
"codersdk.Permission": {
"type": "object",
"properties": {
@@ -23138,14 +22721,6 @@
}
}
},
"codersdk.ResumeTaskResponse": {
"type": "object",
"properties": {
"workspace_build": {
"$ref": "#/definitions/codersdk.WorkspaceBuild"
}
}
},
"codersdk.RetentionConfig": {
"type": "object",
"properties": {
@@ -23471,245 +23046,6 @@
}
}
},
"codersdk.Task": {
"type": "object",
"properties": {
"created_at": {
"type": "string",
"format": "date-time"
},
"current_state": {
"$ref": "#/definitions/codersdk.TaskStateEntry"
},
"display_name": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"initial_prompt": {
"type": "string"
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string",
"format": "uuid"
},
"owner_avatar_url": {
"type": "string"
},
"owner_id": {
"type": "string",
"format": "uuid"
},
"owner_name": {
"type": "string"
},
"status": {
"enum": [
"pending",
"initializing",
"active",
"paused",
"unknown",
"error"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.TaskStatus"
}
]
},
"template_display_name": {
"type": "string"
},
"template_icon": {
"type": "string"
},
"template_id": {
"type": "string",
"format": "uuid"
},
"template_name": {
"type": "string"
},
"template_version_id": {
"type": "string",
"format": "uuid"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"workspace_agent_health": {
"$ref": "#/definitions/codersdk.WorkspaceAgentHealth"
},
"workspace_agent_id": {
"format": "uuid",
"allOf": [
{
"$ref": "#/definitions/uuid.NullUUID"
}
]
},
"workspace_agent_lifecycle": {
"$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle"
},
"workspace_app_id": {
"format": "uuid",
"allOf": [
{
"$ref": "#/definitions/uuid.NullUUID"
}
]
},
"workspace_build_number": {
"type": "integer"
},
"workspace_id": {
"format": "uuid",
"allOf": [
{
"$ref": "#/definitions/uuid.NullUUID"
}
]
},
"workspace_name": {
"type": "string"
},
"workspace_status": {
"enum": [
"pending",
"starting",
"running",
"stopping",
"stopped",
"failed",
"canceling",
"canceled",
"deleting",
"deleted"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.WorkspaceStatus"
}
]
}
}
},
"codersdk.TaskLogEntry": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"id": {
"type": "integer"
},
"time": {
"type": "string",
"format": "date-time"
},
"type": {
"$ref": "#/definitions/codersdk.TaskLogType"
}
}
},
"codersdk.TaskLogType": {
"type": "string",
"enum": ["input", "output"],
"x-enum-varnames": ["TaskLogTypeInput", "TaskLogTypeOutput"]
},
"codersdk.TaskLogsResponse": {
"type": "object",
"properties": {
"logs": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.TaskLogEntry"
}
},
"snapshot": {
"type": "boolean"
},
"snapshot_at": {
"type": "string"
}
}
},
"codersdk.TaskSendRequest": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
}
},
"codersdk.TaskState": {
"type": "string",
"enum": ["working", "idle", "complete", "failed"],
"x-enum-varnames": [
"TaskStateWorking",
"TaskStateIdle",
"TaskStateComplete",
"TaskStateFailed"
]
},
"codersdk.TaskStateEntry": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"state": {
"$ref": "#/definitions/codersdk.TaskState"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"uri": {
"type": "string"
}
}
},
"codersdk.TaskStatus": {
"type": "string",
"enum": [
"pending",
"initializing",
"active",
"paused",
"unknown",
"error"
],
"x-enum-varnames": [
"TaskStatusPending",
"TaskStatusInitializing",
"TaskStatusActive",
"TaskStatusPaused",
"TaskStatusUnknown",
"TaskStatusError"
]
},
"codersdk.TasksListResponse": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"codersdk.TelemetryConfig": {
"type": "object",
"properties": {
@@ -25117,14 +24453,6 @@
}
}
},
"codersdk.UpdateTaskInputRequest": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
}
},
"codersdk.UpdateTemplateACL": {
"type": "object",
"properties": {
+51 -36
View File
@@ -785,7 +785,7 @@ func New(options *Options) *API {
Entitlements: options.Entitlements,
Telemetry: options.Telemetry,
Logger: options.Logger.Named("site"),
HideAITasks: options.DeploymentValues.HideAITasks.Value(),
AITasksEnabled: options.DeploymentValues.EnableAITasks.Value(),
AIGatewayEnabled: options.DeploymentValues.AI.BridgeConfig.Enabled.Value(),
})
if err != nil {
@@ -1329,27 +1329,33 @@ func New(options *Options) *API {
// 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)
//
// Coder Tasks is hidden unless the deployment opts in, so the routes are
// only registered when CODER_ENABLE_AI_TASKS is set. Requests to an
// unregistered path fall through to the route not found handler above.
if options.DeploymentValues.EnableAITasks {
r.Route("/tasks", func(r chi.Router) {
r.Use(apiKeyMiddleware)
r.Get("/", api.tasksList)
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("/{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)
r.Post("/pause", api.pauseTask)
r.Post("/resume", api.resumeTask)
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)
r.Post("/pause", api.pauseTask)
r.Post("/resume", api.resumeTask)
})
})
})
})
}
r.Route("/users/{user}/skills", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
@@ -1933,9 +1939,13 @@ func New(options *Options) *API {
r.Route("/experimental", func(r chi.Router) {
r.Post("/chat-context/refresh", api.workspaceAgentRefreshChatContext)
})
r.Route("/tasks/{task}", func(r chi.Router) {
r.Post("/log-snapshot", api.postWorkspaceAgentTaskLogSnapshot)
})
// Agent-side Coder Tasks reporting, registered only when the
// deployment opts in, for the same reason as the /tasks trees.
if options.DeploymentValues.EnableAITasks {
r.Route("/tasks/{task}", func(r chi.Router) {
r.Post("/log-snapshot", api.postWorkspaceAgentTaskLogSnapshot)
})
}
})
r.Route("/{workspaceagent}", func(r chi.Router) {
r.Use(
@@ -2205,27 +2215,32 @@ func New(options *Options) *API {
r.Get("/{os}/{arch}", api.initScript)
})
r.Route("/ai/providers", aiProvidersHandler(api, apiKeyMiddleware))
r.Route("/tasks", func(r chi.Router) {
r.Use(apiKeyMiddleware)
// Coder Tasks is hidden unless the deployment opts in, so the routes are
// only registered when CODER_ENABLE_AI_TASKS is set. Requests to an
// unregistered path fall through to the route not found handler above.
if options.DeploymentValues.EnableAITasks {
r.Route("/tasks", func(r chi.Router) {
r.Use(apiKeyMiddleware)
r.Get("/", api.tasksList)
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("/{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)
r.Post("/pause", api.pauseTask)
r.Post("/resume", api.resumeTask)
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)
r.Post("/pause", api.pauseTask)
r.Post("/resume", api.resumeTask)
})
})
})
})
}
})
if options.SwaggerEndpoint {
+4
View File
@@ -1866,6 +1866,10 @@ func DeploymentValues(t testing.TB, mut ...func(*codersdk.DeploymentValues)) *co
opts := cfg.Options()
err := opts.SetDefaults()
require.NoError(t, err)
// Tasks ship disabled. Tests exercise the enabled behavior by default so
// the Tasks suite keeps running; tests for the disabled path opt out
// explicitly via the mutators.
cfg.EnableAITasks = true
for _, fn := range mut {
fn(cfg)
}
+7 -1
View File
@@ -20,7 +20,13 @@ func TestEndpointsDocumented(t *testing.T) {
require.NoError(t, err, "can't parse swagger comments")
require.NotEmpty(t, swaggerComments, "swagger comments must be present")
_, _, api := coderdtest.NewWithAPI(t, nil)
// Coder Tasks has no swagger annotations because it is withdrawn from the
// product, so verify against a deployment where its routes are not
// registered.
values := coderdtest.DeploymentValues(t)
values.EnableAITasks = false
_, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{DeploymentValues: values})
coderdtest.VerifySwaggerDefinitions(t, api.APIHandler, swaggerComments, coderdtest.WithSwaggerRoutePrefix("/api/v2"))
}