Files
coder/coderd/database/queries/chatmodeloverrides.sql
T
Michael SuchaczandEthan Dickson 0ef331fcd0 feat!: scope chat model override settings to organizations (#28442)
Moves chat model override settings from deployment scope to organization
scope, following the org-scoping of chat model configs in #28440.

Depends on #28440 (based on
`ethan/chat-models-availability-consolidation`).

Remote dogfood UAT passed on this change, including multi-organization
scenarios on a licensed deployment with real models.

## Problem

Chat model configs are now owned by organizations, but the override
settings that reference them stayed deployment-wide: admin
subagent/title/compaction overrides in `site_configs`, personal
overrides in `user_configs`, and the advisor model embedded in the
advisor runtime JSON. A single deployment-level override can only point
at one organization's model config, so overrides effectively worked for
the default organization only, and title generation had to soften its
hard-failure contract to tolerate unusable overrides.

## Fix

Admin overrides (general, explore, title generation, compaction,
advisor) and personal overrides (root, general, explore) become
organization-scoped rows in two new typed tables,
`chat_organization_model_overrides` and `chat_user_model_overrides`.
Composite foreign keys on `(organization_id, model_config_id)` make
cross-organization model references unrepresentable. The migration
deletes the legacy serialized `site_configs`/`user_configs` override
keys instead of migrating them; admins and users re-select models in the
new organization-scoped settings.

New endpoints live under
`/api/experimental/organizations/{organization}/chats/model-overrides`
(admin defaults) and
`/api/experimental/organizations/{organization}/members/{user}/chats/model-overrides`
(personal overrides); the legacy deployment-level routes are removed.
chatd resolves overrides via the chat's organization, restoring the
hard-failure contract for configured-but-unusable title generation
overrides. The deployment-level `AllowUsers` switch and advisor runtime
limits stay deployment-scoped.

The Coder Agents settings UI gains a per-organization "Defaults &
overrides" page under AI Settings > Models, and the personal overrides
page gains an organization selector for users in multiple organizations.
Advisor telemetry now reports per-organization override rows.

**BREAKING CHANGE**: Existing deployment-level overrides are dropped,
not migrated. Every organization starts with no overrides after upgrade,
and model selections must be re-applied in the new settings. The legacy
deployment-level override endpoints are removed.

Closes https://linear.app/coder/issue/CODAGT-872

> [!NOTE]
> Xum (AI agent) authored this PR on behalf of @ibetitsmike.

<!-- xum-attribution: model=claude-opus-4-6 thinking=high -->

---------

Co-authored-by: Ethan Dickson <ethan@coder.com>
2026-08-24 11:20:26 +01:00

73 lines
2.9 KiB
SQL

-- name: GetChatOrganizationModelOverrides :many
SELECT id, organization_id, context, model_config_id, reasoning_effort
FROM chat_organization_model_overrides
WHERE organization_id = @organization_id
ORDER BY context;
-- name: GetChatOrganizationModelOverride :one
SELECT id, organization_id, context, model_config_id, reasoning_effort
FROM chat_organization_model_overrides
WHERE organization_id = @organization_id
AND context = @context;
-- name: GetChatOrganizationModelOverridesByContext :many
-- Returns every non-deleted organization's override for one context together
-- with the resolved model and provider, for bulk consumers such as telemetry.
-- model_available mirrors GetEnabledChatModelConfigByID: it is false when the
-- referenced config or its provider is disabled or deleted.
SELECT
o.organization_id,
(cmc.id IS NOT NULL AND ap.id IS NOT NULL)::boolean AS model_available,
COALESCE(cmc.model, '')::text AS model,
COALESCE(ap.type::text, '')::text AS provider_type
FROM chat_organization_model_overrides o
JOIN organizations org ON org.id = o.organization_id AND NOT org.deleted
LEFT JOIN chat_model_configs cmc
ON cmc.id = o.model_config_id
AND cmc.deleted = FALSE
AND cmc.enabled = TRUE
LEFT JOIN ai_providers ap
ON ap.id = cmc.ai_provider_id
AND ap.enabled = TRUE
AND ap.deleted = FALSE
WHERE o.context = @context
ORDER BY o.organization_id;
-- name: UpsertChatOrganizationModelOverride :exec
INSERT INTO chat_organization_model_overrides
(organization_id, context, model_config_id, reasoning_effort)
VALUES (@organization_id, @context, @model_config_id, @reasoning_effort)
ON CONFLICT ON CONSTRAINT chat_organization_model_overrides_organization_id_context_key
DO UPDATE SET
model_config_id = EXCLUDED.model_config_id,
reasoning_effort = EXCLUDED.reasoning_effort;
-- name: DeleteChatOrganizationModelOverride :exec
DELETE FROM chat_organization_model_overrides
WHERE organization_id = @organization_id
AND context = @context;
-- name: GetChatUserModelOverrides :many
SELECT id, user_id, organization_id, context, mode, model_config_id, reasoning_effort
FROM chat_user_model_overrides
WHERE user_id = @user_id
AND organization_id = @organization_id
ORDER BY context;
-- name: GetChatUserModelOverride :one
SELECT id, user_id, organization_id, context, mode, model_config_id, reasoning_effort
FROM chat_user_model_overrides
WHERE user_id = @user_id
AND organization_id = @organization_id
AND context = @context;
-- name: UpsertChatUserModelOverride :exec
INSERT INTO chat_user_model_overrides
(user_id, organization_id, context, mode, model_config_id, reasoning_effort)
VALUES (@user_id, @organization_id, @context, @mode, @model_config_id, @reasoning_effort)
ON CONFLICT ON CONSTRAINT chat_user_model_overrides_user_organization_context_key
DO UPDATE SET
mode = EXCLUDED.mode,
model_config_id = EXCLUDED.model_config_id,
reasoning_effort = EXCLUDED.reasoning_effort;