EXPERIMENTAL: this endpoint is experimental and is subject to change. @Summary Send chat message @ID send-chat-message @Security CoderSessionToken @Tags Chats @Accept json @Produce json @Param chat path string true "Chat ID" format(uuid) @Param request body codersdk.CreateChatMessageRequest true "C
(rw http.ResponseWriter, r *http.Request)
| 2966 | // @Router /api/experimental/chats/{chat}/messages [post] |
| 2967 | // @Description Experimental: this endpoint is subject to change. |
| 2968 | func (api *API) postChatMessages(rw http.ResponseWriter, r *http.Request) { |
| 2969 | ctx := r.Context() |
| 2970 | apiKey := httpmw.APIKey(r) |
| 2971 | chat := httpmw.ChatParam(r) |
| 2972 | chatID := chat.ID |
| 2973 | |
| 2974 | // Sending a message triggers LLM inference, requiring update |
| 2975 | // permission on the org-scoped chat resource. |
| 2976 | if !api.Authorize(r, policy.ActionUpdate, chat.RBACObject()) { |
| 2977 | httpapi.ResourceNotFound(rw) |
| 2978 | return |
| 2979 | } |
| 2980 | |
| 2981 | // Only the chat owner may send messages. Org admins pass the |
| 2982 | // RBAC check above (org-level ActionUpdate), but chat |
| 2983 | // processing forwards the *owner's* credentials (OIDC tokens, |
| 2984 | // provider API keys) to external services. Allowing a |
| 2985 | // non-owner to trigger processing would leak the owner's |
| 2986 | // tokens to MCP servers the caller controls. |
| 2987 | if apiKey.UserID != chat.OwnerID { |
| 2988 | httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ |
| 2989 | Message: "Only the chat owner may send messages.", |
| 2990 | }) |
| 2991 | return |
| 2992 | } |
| 2993 | |
| 2994 | if chat.Archived { |
| 2995 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 2996 | Message: "Cannot send messages to an archived chat.", |
| 2997 | }) |
| 2998 | return |
| 2999 | } |
| 3000 | |
| 3001 | if api.chatDaemon == nil { |
| 3002 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 3003 | Message: "Chat processor is unavailable.", |
| 3004 | Detail: "Chat processor is not configured.", |
| 3005 | }) |
| 3006 | return |
| 3007 | } |
| 3008 | |
| 3009 | var req codersdk.CreateChatMessageRequest |
| 3010 | if !httpapi.Read(ctx, rw, r, &req) { |
| 3011 | return |
| 3012 | } |
| 3013 | |
| 3014 | contentBlocks, _, fileIDs, inputError := createChatInputFromParts(ctx, api.Database, req.Content, "content") |
| 3015 | if inputError != nil { |
| 3016 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 3017 | Message: inputError.Message, |
| 3018 | Detail: inputError.Detail, |
| 3019 | }) |
| 3020 | return |
| 3021 | } |
| 3022 | |
| 3023 | // Validate MCP server IDs exist. |
| 3024 | if req.MCPServerIDs != nil && len(*req.MCPServerIDs) > 0 { |
| 3025 | //nolint:gocritic // Need to validate MCP server IDs exist. |
nothing calls this directly
no test coverage detected