Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 89 additions & 11 deletions coderd/exp_chats.go
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,21 @@ func (api *API) validateUserChatModelConfigAvailable(
}
}

// validateExplicitChatModelConfigAvailable validates a caller-supplied
// model config ID. A nil ID keeps the chat's current model and is
// validated by the daemon's fallback resolution instead.
func (api *API) validateExplicitChatModelConfigAvailable(
ctx context.Context,
userID uuid.UUID,
modelConfigID uuid.UUID,
) (int, *codersdk.Response) {
if modelConfigID == uuid.Nil {
return 0, nil
Comment thread
ibetitsmike marked this conversation as resolved.
}
_, status, resp := api.validateUserChatModelConfigAvailable(ctx, userID, modelConfigID)
return status, resp
}

// EXPERIMENTAL: this endpoint is experimental and is subject to change.
//
// @Summary Create chat
Expand Down Expand Up @@ -1431,6 +1446,13 @@ func (api *API) postChats(rw http.ResponseWriter, r *http.Request) {
if maybeWriteLimitErr(ctx, rw, err) {
return
}
if xerrors.Is(err, chatd.ErrInvalidModelConfigID) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid model config ID.",
Detail: err.Error(),
})
return
}
if database.IsForeignKeyViolation(
err,
database.ForeignKeyChatsLastModelConfigID,
Expand Down Expand Up @@ -3336,6 +3358,10 @@ func (api *API) postChatMessages(rw http.ResponseWriter, r *http.Request) {
if req.ModelConfigID != nil {
modelConfigID = *req.ModelConfigID
}
if status, resp := api.validateExplicitChatModelConfigAvailable(ctx, apiKey.UserID, modelConfigID); resp != nil {
Comment thread
ibetitsmike marked this conversation as resolved.
httpapi.Write(ctx, rw, status, *resp)
return
}

reasoningEffort := req.ReasoningEffort
if reasoningEffort != nil && !chatprovider.IsValidReasoningEffort(*reasoningEffort) {
Expand Down Expand Up @@ -3385,6 +3411,12 @@ func (api *API) postChatMessages(rw http.ResponseWriter, r *http.Request) {
})
return
}
if xerrors.Is(sendErr, chatd.ErrNoDefaultChatModelConfig) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "No default chat model config is configured.",
})
return
}
if errors.Is(sendErr, chatstate.ErrChatNotFound) {
httpapi.ResourceNotFound(rw)
return
Expand Down Expand Up @@ -3501,6 +3533,10 @@ func (api *API) patchChatMessage(rw http.ResponseWriter, r *http.Request) {
if req.ModelConfigID != nil {
editModelConfigID = *req.ModelConfigID
}
if status, resp := api.validateExplicitChatModelConfigAvailable(ctx, apiKey.UserID, editModelConfigID); resp != nil {
Comment thread
ibetitsmike marked this conversation as resolved.
httpapi.Write(ctx, rw, status, *resp)
return
}

editReasoningEffort := req.ReasoningEffort
if editReasoningEffort != nil && !chatprovider.IsValidReasoningEffort(*editReasoningEffort) {
Expand Down Expand Up @@ -3540,6 +3576,10 @@ func (api *API) patchChatMessage(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid model config ID.",
})
case xerrors.Is(editErr, chatd.ErrNoDefaultChatModelConfig):
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "No default chat model config is configured.",
})
case errors.Is(editErr, chatstate.ErrChatNotFound):
httpapi.ResourceNotFound(rw)
case writeChatInvalidState(ctx, rw, editErr):
Expand Down Expand Up @@ -4818,6 +4858,9 @@ func (api *API) resolveCreateChatModelConfigID(
Message: "Invalid model config ID.",
}
}
if _, status, resp := api.validateUserChatModelConfigAvailable(ctx, userID, *req.ModelConfigID); resp != nil {
return uuid.Nil, nil, status, resp
}
return *req.ModelConfigID, nil, 0, nil
Comment thread
ibetitsmike marked this conversation as resolved.
}

Expand Down Expand Up @@ -4912,6 +4955,21 @@ func (api *API) defaultCreateChatModelConfigID(
}
}

// The resolved default may itself be disabled or under a disabled
// provider.
if _, err := lookupEnabledChatModelConfigByID(ctx, api.Database, defaultModelConfig.ID); err != nil {
Comment thread
ibetitsmike marked this conversation as resolved.
if xerrors.Is(err, sql.ErrNoRows) {
return uuid.Nil, http.StatusBadRequest, &codersdk.Response{
Message: "No default chat model config is configured.",
Detail: "The default chat model or its provider is disabled.",
}
}
return uuid.Nil, http.StatusInternalServerError, &codersdk.Response{
Message: "Failed to resolve chat model config.",
Detail: err.Error(),
}
}

return defaultModelConfig.ID, 0, nil
}

Expand Down Expand Up @@ -5703,16 +5761,11 @@ func (api *API) putChatAdvisorConfig(rw http.ResponseWriter, r *http.Request) {
return
}
} else {
// Use system context because GetChatModelConfigByID requires
// deployment-config read access, which can be broader than the
// handler's explicit update check. The lookup validates the model and
// any selected reasoning effort before persisting deployment config.
//nolint:gocritic // This admin-authorized validation lookup intentionally bypasses read authz.
modelConfig, err := api.Database.GetChatModelConfigByID(dbauthz.AsSystemRestricted(ctx), req.ModelConfigID)
modelConfig, err := lookupEnabledChatModelConfigByID(ctx, api.Database, req.ModelConfigID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) || httpapi.Is404Error(err) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("model_config_id %q does not match any existing model config.", req.ModelConfigID),
Message: fmt.Sprintf("model_config_id %q does not match any enabled model config.", req.ModelConfigID),
})
return
}
Expand Down Expand Up @@ -7625,20 +7678,45 @@ func ensureDefaultChatModelConfig(
return nil
}

candidateConfig := modelConfigs[0]
// Prefer a config that can actually serve requests (enabled, under an
// enabled provider) so the promoted default does not reject
// omitted-model chat creation. Fall back to any non-excluded config
// when no usable candidate exists.
//nolint:gocritic // Candidate usability depends on deployment-wide provider state, not the caller's permissions.
enabledRows, err := tx.GetEnabledChatModelConfigs(dbauthz.AsChatd(ctx))
if err != nil {
return xerrors.Errorf("list enabled chat model configs: %w", err)
}
usable := make(map[uuid.UUID]struct{}, len(enabledRows))
for _, row := range enabledRows {
usable[row.ChatModelConfig.ID] = struct{}{}
}

excluded := make(map[uuid.UUID]struct{}, len(excludedConfigIDs))
for _, configID := range excludedConfigIDs {
if configID == uuid.Nil {
continue
}
excluded[configID] = struct{}{}
}
for _, config := range modelConfigs {

candidateConfig := modelConfigs[0]
var selected *database.ChatModelConfig
for i := range modelConfigs {
config := &modelConfigs[i]
if _, skip := excluded[config.ID]; skip {
continue
}
candidateConfig = config
break
if selected == nil {
selected = config
}
if _, ok := usable[config.ID]; ok {
selected = config
break
}
}
if selected != nil {
candidateConfig = *selected
}

if err := tx.UnsetDefaultChatModelConfigs(ctx); err != nil {
Expand Down
Loading
Loading