EXPERIMENTAL: this endpoint is experimental and is subject to change. @Summary Create chat @ID create-chat @Security CoderSessionToken @Tags Chats @Accept json @Produce json @Param request body codersdk.CreateChatRequest true "Create chat request" @Success 201 {object} codersdk.Chat @Router /api/ex
(rw http.ResponseWriter, r *http.Request)
| 1013 | // @Router /api/experimental/chats [post] |
| 1014 | // @Description Experimental: this endpoint is subject to change. |
| 1015 | func (api *API) postChats(rw http.ResponseWriter, r *http.Request) { |
| 1016 | ctx := r.Context() |
| 1017 | apiKey := httpmw.APIKey(r) |
| 1018 | |
| 1019 | // Cap the raw request body to prevent excessive memory use |
| 1020 | // from large dynamic tool schemas. |
| 1021 | r.Body = http.MaxBytesReader(rw, r.Body, int64(2*maxSystemPromptLenBytes)) |
| 1022 | |
| 1023 | var req codersdk.CreateChatRequest |
| 1024 | if !httpapi.Read(ctx, rw, r, &req) { |
| 1025 | return |
| 1026 | } |
| 1027 | |
| 1028 | aReq, commitAudit := audit.InitRequest[database.Chat](rw, &audit.RequestParams{ |
| 1029 | Audit: *api.Auditor.Load(), |
| 1030 | Log: api.Logger, |
| 1031 | Request: r, |
| 1032 | Action: database.AuditActionCreate, |
| 1033 | OrganizationID: req.OrganizationID, |
| 1034 | }) |
| 1035 | defer commitAudit() |
| 1036 | |
| 1037 | // Validate organization membership. |
| 1038 | if req.OrganizationID == uuid.Nil { |
| 1039 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 1040 | Message: "organization_id is required.", |
| 1041 | }) |
| 1042 | return |
| 1043 | } |
| 1044 | isMember, err := httpmw.UserAuthorization(ctx).HasOrganizationMembership(req.OrganizationID) |
| 1045 | if err != nil { |
| 1046 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 1047 | Message: "Failed to validate organization membership.", |
| 1048 | Detail: xerrors.Errorf("check organization membership: %w", err).Error(), |
| 1049 | }) |
| 1050 | return |
| 1051 | } |
| 1052 | if !isMember { |
| 1053 | httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ |
| 1054 | Message: "You are not a member of the specified organization.", |
| 1055 | }) |
| 1056 | return |
| 1057 | } |
| 1058 | // NOTE: This authorize check is intentionally placed after request |
| 1059 | // parsing because we need req.OrganizationID to scope the RBAC check |
| 1060 | // to the correct org. The request body is bounded by MaxBytesReader |
| 1061 | // above, limiting the cost of parsing before rejection. |
| 1062 | if !api.Authorize(r, policy.ActionCreate, rbac.ResourceChat.WithOwner(apiKey.UserID.String()).InOrg(req.OrganizationID)) { |
| 1063 | httpapi.Forbidden(rw) |
| 1064 | return |
| 1065 | } |
| 1066 | |
| 1067 | // Validate per-chat system prompt length. |
| 1068 | const maxSystemPromptLen = 10000 |
| 1069 | if len(req.SystemPrompt) > maxSystemPromptLen { |
| 1070 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 1071 | Message: "System prompt exceeds maximum length.", |
| 1072 | Detail: fmt.Sprintf("System prompt must be at most %d characters, got %d.", maxSystemPromptLen, len(req.SystemPrompt)), |
nothing calls this directly
no test coverage detected