mirror of
https://github.com/coder/coder.git
synced 2026-06-05 14:08:20 +00:00
8b1705eb65
## 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.
94 lines
2.6 KiB
Go
94 lines
2.6 KiB
Go
package chatd
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"charm.land/fantasy"
|
|
"github.com/google/uuid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chatdebug"
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
|
|
)
|
|
|
|
type directModelRoute struct {
|
|
ProviderHint string
|
|
Keys chatprovider.ProviderAPIKeys
|
|
}
|
|
|
|
func (*Server) newDirectModel(
|
|
_ context.Context,
|
|
req modelClientRequest,
|
|
route directModelRoute,
|
|
opts modelBuildOptions,
|
|
) (fantasy.LanguageModel, error) {
|
|
var httpClient *http.Client
|
|
if opts.RecordHTTP {
|
|
httpClient = &http.Client{Transport: &chatdebug.RecordingTransport{}}
|
|
}
|
|
return newLanguageModel(
|
|
route.ProviderHint,
|
|
req.ModelName,
|
|
route.Keys,
|
|
req.UserAgent,
|
|
req.ExtraHeaders,
|
|
httpClient,
|
|
)
|
|
}
|
|
|
|
func (p *Server) resolveDirectModelRouteForConfig(
|
|
ctx context.Context,
|
|
ownerID uuid.UUID,
|
|
modelConfig database.ChatModelConfig,
|
|
fallbackKeys chatprovider.ProviderAPIKeys,
|
|
) (resolvedModelRoute, error) {
|
|
providerHint, provider, err := p.directProviderHintAndProviderForConfig(ctx, modelConfig)
|
|
if err != nil {
|
|
return resolvedModelRoute{}, err
|
|
}
|
|
if provider == nil {
|
|
if !fallbackKeys.Empty() && userCanUseProviderKeys(fallbackKeys, providerHint) {
|
|
return newDirectModelRoute(providerHint, fallbackKeys), nil
|
|
}
|
|
keys, err := p.resolveUserProviderAPIKeys(ctx, ownerID, uuid.Nil)
|
|
if err != nil {
|
|
return resolvedModelRoute{}, xerrors.Errorf("resolve provider API keys: %w", err)
|
|
}
|
|
return newDirectModelRoute(providerHint, keys), nil
|
|
}
|
|
providerKeys, err := p.resolveUserProviderAPIKeysForProvider(ctx, ownerID, *provider)
|
|
if err != nil {
|
|
return resolvedModelRoute{}, xerrors.Errorf("resolve provider API keys: %w", err)
|
|
}
|
|
return newDirectModelRoute(providerHint, providerKeys), nil
|
|
}
|
|
|
|
func (p *Server) resolveDirectModelRouteForProviderType(
|
|
ctx context.Context,
|
|
ownerID uuid.UUID,
|
|
providerType string,
|
|
) (resolvedModelRoute, error) {
|
|
normalizedProviderType := chatprovider.NormalizeProvider(providerType)
|
|
keys, _, err := p.resolveUserProviderAPIKeysAndProviderForProviderType(ctx, ownerID, providerType)
|
|
if err != nil {
|
|
return resolvedModelRoute{}, err
|
|
}
|
|
return newDirectModelRoute(normalizedProviderType, keys), nil
|
|
}
|
|
|
|
func (p *Server) directProviderHintAndProviderForConfig(
|
|
ctx context.Context,
|
|
modelConfig database.ChatModelConfig,
|
|
) (string, *database.AIProvider, error) {
|
|
if !modelConfig.AIProviderID.Valid {
|
|
return modelConfig.Provider, nil, nil
|
|
}
|
|
provider, err := p.enabledAIProviderByID(ctx, modelConfig.AIProviderID.UUID)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
return string(provider.Type), &provider, nil
|
|
}
|