authAndDoWithTaskAppClient centralizes the shared logic to: - Fetch the task workspace - Authorize ApplicationConnect on the workspace - Validate the AI task and task app health - Dial the agent and construct an HTTP client to the apps loopback URL The provided callback receives the context, an HT
( r *http.Request, task database.Task, do func(ctx context.Context, client *http.Client, appURL *url.URL) error, )
| 982 | // The provided callback receives the context, an HTTP client that dials via the |
| 983 | // agent, and the base app URL (as a value URL) to perform any request. |
| 984 | func (api *API) authAndDoWithTaskAppClient( |
| 985 | r *http.Request, |
| 986 | task database.Task, |
| 987 | do func(ctx context.Context, client *http.Client, appURL *url.URL) error, |
| 988 | ) error { |
| 989 | ctx := r.Context() |
| 990 | |
| 991 | if task.Status != database.TaskStatusActive { |
| 992 | // Return 409 Conflict for valid requests blocked by current state |
| 993 | // (pending/initializing are transitional, paused requires resume). |
| 994 | // Return 400 Bad Request for error/unknown states. |
| 995 | switch task.Status { |
| 996 | case database.TaskStatusPending, database.TaskStatusInitializing: |
| 997 | return httperror.NewResponseError(http.StatusConflict, codersdk.Response{ |
| 998 | Message: fmt.Sprintf("Task is %s.", task.Status), |
| 999 | Detail: "The task is resuming. Wait for the task to become active before sending messages.", |
| 1000 | }) |
| 1001 | case database.TaskStatusPaused: |
| 1002 | return httperror.NewResponseError(http.StatusConflict, codersdk.Response{ |
| 1003 | Message: "Task is paused.", |
| 1004 | Detail: "Resume the task to send messages.", |
| 1005 | }) |
| 1006 | default: |
| 1007 | // Default handler for error and unknown status. |
| 1008 | return httperror.NewResponseError(http.StatusBadRequest, codersdk.Response{ |
| 1009 | Message: "Task must be active.", |
| 1010 | Detail: fmt.Sprintf("Task status is %q, it must be %q to interact with the task.", task.Status, codersdk.TaskStatusActive), |
| 1011 | }) |
| 1012 | } |
| 1013 | } |
| 1014 | if !task.WorkspaceID.Valid { |
| 1015 | return httperror.NewResponseError(http.StatusBadRequest, codersdk.Response{ |
| 1016 | Message: "Task does not have a workspace.", |
| 1017 | }) |
| 1018 | } |
| 1019 | if !task.WorkspaceAppID.Valid { |
| 1020 | return httperror.NewResponseError(http.StatusBadRequest, codersdk.Response{ |
| 1021 | Message: "Task does not have a workspace app.", |
| 1022 | }) |
| 1023 | } |
| 1024 | |
| 1025 | workspace, err := api.Database.GetWorkspaceByID(ctx, task.WorkspaceID.UUID) |
| 1026 | if err != nil { |
| 1027 | if httpapi.Is404Error(err) { |
| 1028 | return httperror.ErrResourceNotFound |
| 1029 | } |
| 1030 | return httperror.NewResponseError(http.StatusInternalServerError, codersdk.Response{ |
| 1031 | Message: "Internal error fetching workspace.", |
| 1032 | Detail: err.Error(), |
| 1033 | }) |
| 1034 | } |
| 1035 | |
| 1036 | // Connecting to applications requires ApplicationConnect on the workspace. |
| 1037 | if !api.Authorize(r, policy.ActionApplicationConnect, workspace) { |
| 1038 | return httperror.ErrResourceNotFound |
| 1039 | } |
| 1040 | |
| 1041 | apps, err := api.Database.GetWorkspaceAppsByAgentID(ctx, task.WorkspaceAgentID.UUID) |
no test coverage detected