mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
## Summary Routes chatd model calls backed by concrete AI Provider rows through the in-process aibridge transport by default, with deployment options to use direct provider routing when AI Gateway is disabled or chat AI Gateway routing is disabled. - Splits model routing into common, direct provider, and AI Gateway paths behind a single deployment-mode entry point. - Builds chatd models through explicit request, route, and options data. Active API key attribution is passed explicitly instead of being hidden inside generic model construction. - For AI Gateway BYOK routes, resolves the user's provider key in chatd, forwards it through provider-specific auth headers, and sets `X-Coder-AI-Governance-Token` to the `delegated` marker so aibridge preserves those headers while still stripping Coder-specific metadata. - Keeps central provider credentials and deployment fallback credentials out of forwarded provider auth headers, so AI Gateway central policy remains authoritative. - Redacts delegated provider auth from default string formatting to avoid accidental plaintext logging of user BYOK credentials. - Covers selected chat models, advisor overrides, title and quickgen paths, subagent overrides, computer use model selection, and an integration-style chat turn through the aibridge transport path. - Persists initiating API key IDs on chat and queued user messages, including subagent child messages, and fails closed for AI Gateway-routed model builds without an active key. - Removes unused `api_key_id` indexes while keeping the persistence columns and foreign keys. - Keeps the deployment option available through config and env parsing, but hides it from CLI help and generated docs. - Stabilizes the subagent poll fallback test so background CreateChat processing cannot win the state transition under slower CI environments. ## Tests - `go test ./coderd/x/chatd -run 'TestAIGatewayProviderAuthForUser|TestAIGatewayProviderAuthRedactsFormatting|TestResolveModelRouteForConfigAIGatewayProviderAuth|TestAIGatewayModelForwardsProviderAuth|TestProcessChat_AIGatewayRoutingUsesDelegatedAPIKey|TestAwaitSubagentCompletion' -count=1` - `go test ./coderd/aibridged -run 'TestServeHTTP_DelegatedAPIKey|TestServeHTTP_StripCoderToken' -count=1` - `git diff --check HEAD~1..HEAD` - `make lint` > Mux working on behalf of Mike.
102 lines
3.4 KiB
Go
102 lines
3.4 KiB
Go
package chatd
|
|
|
|
import (
|
|
"context"
|
|
|
|
"charm.land/fantasy"
|
|
"github.com/google/uuid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
|
|
)
|
|
|
|
const titleGenerationOverrideContext = "title_generation"
|
|
|
|
func readTitleGenerationModelOverride(
|
|
ctx context.Context,
|
|
db database.Store,
|
|
) (string, error) {
|
|
//nolint:gocritic // Chatd is internal, not a user, so this read uses AsChatd.
|
|
chatdCtx := dbauthz.AsChatd(ctx)
|
|
raw, err := db.GetChatTitleGenerationModelOverride(chatdCtx)
|
|
if err != nil {
|
|
return "", xerrors.Errorf(
|
|
"get chat title generation model override: %w",
|
|
err,
|
|
)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
// resolveTitleGenerationModelOverride resolves the deployment-wide title
|
|
// generation model override. overrideSet is true when an override was
|
|
// configured; in that case any returned error is a hard failure. When
|
|
// overrideSet is false, callers may fall back to the default title model.
|
|
func (p *Server) resolveTitleGenerationModelOverride(
|
|
ctx context.Context,
|
|
chat database.Chat,
|
|
keys chatprovider.ProviderAPIKeys,
|
|
modelOpts modelBuildOptions,
|
|
) (database.ChatModelConfig, fantasy.LanguageModel, chatprovider.ProviderAPIKeys, resolvedModelRoute, bool, error) {
|
|
raw, err := readTitleGenerationModelOverride(ctx, p.db)
|
|
if err != nil {
|
|
return database.ChatModelConfig{}, nil, chatprovider.ProviderAPIKeys{}, resolvedModelRoute{}, false, xerrors.Errorf(
|
|
"read title generation model override: %w",
|
|
err,
|
|
)
|
|
}
|
|
|
|
overrideProviderKeys := keys
|
|
modelConfig, overrideSet, err := p.resolveConfiguredModelOverride(
|
|
ctx,
|
|
titleGenerationOverrideContext,
|
|
raw,
|
|
chat.OwnerID,
|
|
p.resolveModelConfigAndNormalizedProvider,
|
|
func(ctx context.Context, ownerID uuid.UUID, aiProviderID uuid.UUID) (chatprovider.ProviderAPIKeys, error) {
|
|
if aiProviderID == uuid.Nil {
|
|
resolvedProviderKeys, err := p.resolveUserProviderAPIKeys(ctx, ownerID, uuid.Nil)
|
|
if err != nil || resolvedProviderKeys.Empty() {
|
|
resolvedProviderKeys = keys
|
|
}
|
|
overrideProviderKeys = resolvedProviderKeys
|
|
return resolvedProviderKeys, nil
|
|
}
|
|
resolvedProviderKeys, err := p.resolveUserProviderAPIKeys(ctx, ownerID, aiProviderID)
|
|
if err != nil {
|
|
return chatprovider.ProviderAPIKeys{}, err
|
|
}
|
|
overrideProviderKeys = resolvedProviderKeys
|
|
return resolvedProviderKeys, nil
|
|
},
|
|
modelOverrideFailureModeHard,
|
|
)
|
|
if err != nil {
|
|
return database.ChatModelConfig{}, nil, chatprovider.ProviderAPIKeys{}, resolvedModelRoute{}, overrideSet, err
|
|
}
|
|
if !overrideSet {
|
|
return database.ChatModelConfig{}, nil, keys, resolvedModelRoute{}, false, nil
|
|
}
|
|
|
|
//nolint:gocritic // Title overrides need chatd-scoped provider reads for user-owned chats.
|
|
route, err := p.resolveModelRouteForConfig(dbauthz.AsChatd(ctx), chat.OwnerID, modelConfig, overrideProviderKeys)
|
|
if err != nil {
|
|
return database.ChatModelConfig{}, nil, chatprovider.ProviderAPIKeys{}, resolvedModelRoute{}, true, err
|
|
}
|
|
model, err := p.newModel(ctx, modelClientRequest{
|
|
Chat: chat,
|
|
ModelName: modelConfig.Model,
|
|
UserAgent: chatprovider.UserAgent(),
|
|
ExtraHeaders: chatprovider.CoderHeaders(chat),
|
|
}, route, modelOpts)
|
|
if err != nil {
|
|
return database.ChatModelConfig{}, nil, chatprovider.ProviderAPIKeys{}, resolvedModelRoute{}, true, xerrors.Errorf(
|
|
"create title generation model override: %w",
|
|
err,
|
|
)
|
|
}
|
|
return modelConfig, model, route.directProviderKeys(), route, true, nil
|
|
}
|