mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: deprecate AIGatewayRoutingEnabled, remove direct chat routing (#26862)
This PR removes the now-dead direct-routing code: - Deletes the direct routing implementation. - Collapses the resolvedModelRoute discriminated union into aiGatewayModelRoute. - Removes the dead providerKeys cascade. - Deletes the preferredShortTextCandidates quickgen function. - Simplifies the advisor override error handling. - Deprecates the AIGatewayRoutingEnabled deployment option. It is now a no-op so as to not break existing deployments on upgrade. Once direct routing was gone, the AI Gateway became mandatory for chat, which surfaced gaps in how the product behaves with the gateway disabled: - Exposes ai-gateway-enabled to the frontend via embedded page metadata. - Disables the chat composer via the existing AgentSetupNotice when the gateway is disabled, for both new and existing chats. - Fixes nil/typed-nil chatDaemon panics on startup and shutdown when gateway is disabled. - Fixes chat WebSocket from retrying the still-gated stream endpoint forever when the gateway is disabled.
This commit is contained in:
+56
-34
@@ -714,6 +714,7 @@ func New(options *Options) *API {
|
||||
Telemetry: options.Telemetry,
|
||||
Logger: options.Logger.Named("site"),
|
||||
HideAITasks: options.DeploymentValues.HideAITasks.Value(),
|
||||
AIGatewayEnabled: options.DeploymentValues.AI.BridgeConfig.Enabled.Value(),
|
||||
})
|
||||
if err != nil {
|
||||
options.Logger.Fatal(ctx, "failed to initialize site handler", slog.Error(err))
|
||||
@@ -825,37 +826,38 @@ func New(options *Options) *API {
|
||||
providerAPIKeys = *options.ChatProviderAPIKeys
|
||||
}
|
||||
|
||||
chatAIGatewayRoutingEnabled := options.DeploymentValues.AI.BridgeConfig.Enabled.Value() &&
|
||||
options.DeploymentValues.AI.Chat.AIGatewayRoutingEnabled.Value()
|
||||
|
||||
api.chatDaemon = chatd.New(options.Pubsub, chatd.Config{
|
||||
Logger: options.Logger.Named("chatd"),
|
||||
Database: options.Database,
|
||||
ReplicaID: api.ID,
|
||||
StreamPartsDialer: options.ChatStreamPartsDialer,
|
||||
MaxChatsPerAcquire: int32(maxChatsPerAcquire), //nolint:gosec // maxChatsPerAcquire is clamped to int32 range above.
|
||||
ProviderAPIKeys: providerAPIKeys,
|
||||
AllowBYOK: options.DeploymentValues.AI.BridgeConfig.AllowBYOK.Value(),
|
||||
AllowBYOKSet: true,
|
||||
AIBridgeTransportFactory: &api.AIBridgeTransportFactory,
|
||||
AIGatewayRoutingEnabled: chatAIGatewayRoutingEnabled,
|
||||
AlwaysEnableDebugLogs: options.DeploymentValues.AI.Chat.DebugLoggingEnabled.Value(),
|
||||
Experiments: experiments,
|
||||
AgentConn: api.agentProvider.AgentConn,
|
||||
AgentInactiveDisconnectTimeout: api.AgentInactiveDisconnectTimeout,
|
||||
InstructionLookupTimeout: options.ChatdInstructionLookupTimeout,
|
||||
CreateWorkspace: api.chatCreateWorkspace,
|
||||
StartWorkspace: api.chatStartWorkspace,
|
||||
StopWorkspace: api.chatStopWorkspace,
|
||||
WebpushDispatcher: options.WebPushDispatcher,
|
||||
UsageTracker: options.WorkspaceUsageTracker,
|
||||
PrometheusRegistry: options.PrometheusRegistry,
|
||||
OIDCTokenSource: oidcMCPSrc,
|
||||
NotificationsEnqueuer: options.NotificationsEnqueuer,
|
||||
Auditor: &api.Auditor,
|
||||
})
|
||||
if !options.ChatWorkerDisabled {
|
||||
api.chatDaemon.Start()
|
||||
// AI Gateway is mandatory for chat. When the bridge is disabled
|
||||
// the chat daemon stays nil and chat HTTP handlers return a
|
||||
// service-unavailable error with a clear remediation message.
|
||||
if options.DeploymentValues.AI.BridgeConfig.Enabled.Value() {
|
||||
api.chatDaemon = chatd.New(options.Pubsub, chatd.Config{
|
||||
Logger: options.Logger.Named("chatd"),
|
||||
Database: options.Database,
|
||||
ReplicaID: api.ID,
|
||||
StreamPartsDialer: options.ChatStreamPartsDialer,
|
||||
MaxChatsPerAcquire: int32(maxChatsPerAcquire), //nolint:gosec // maxChatsPerAcquire is clamped to int32 range above.
|
||||
ProviderAPIKeys: providerAPIKeys,
|
||||
AllowBYOK: options.DeploymentValues.AI.BridgeConfig.AllowBYOK.Value(),
|
||||
AllowBYOKSet: true,
|
||||
AIBridgeTransportFactory: &api.AIBridgeTransportFactory,
|
||||
AlwaysEnableDebugLogs: options.DeploymentValues.AI.Chat.DebugLoggingEnabled.Value(),
|
||||
Experiments: experiments,
|
||||
AgentConn: api.agentProvider.AgentConn,
|
||||
AgentInactiveDisconnectTimeout: api.AgentInactiveDisconnectTimeout,
|
||||
InstructionLookupTimeout: options.ChatdInstructionLookupTimeout,
|
||||
CreateWorkspace: api.chatCreateWorkspace,
|
||||
StartWorkspace: api.chatStartWorkspace,
|
||||
StopWorkspace: api.chatStopWorkspace,
|
||||
WebpushDispatcher: options.WebPushDispatcher,
|
||||
UsageTracker: options.WorkspaceUsageTracker,
|
||||
PrometheusRegistry: options.PrometheusRegistry,
|
||||
OIDCTokenSource: oidcMCPSrc,
|
||||
NotificationsEnqueuer: options.NotificationsEnqueuer,
|
||||
Auditor: &api.Auditor,
|
||||
})
|
||||
if !options.ChatWorkerDisabled {
|
||||
api.chatDaemon.Start()
|
||||
}
|
||||
}
|
||||
gitSyncLogger := options.Logger.Named("gitsync")
|
||||
refresher := gitsync.NewRefresher(
|
||||
@@ -864,9 +866,10 @@ func New(options *Options) *API {
|
||||
gitSyncLogger.Named("refresher"),
|
||||
quartz.NewReal(),
|
||||
)
|
||||
publishDiffStatusChange := chatDaemonPublishDiffStatusChangeFunc(api.chatDaemon)
|
||||
api.gitSyncWorker = gitsync.NewWorker(options.Database,
|
||||
refresher,
|
||||
api.chatDaemon.PublishDiffStatusChange,
|
||||
publishDiffStatusChange,
|
||||
quartz.NewReal(),
|
||||
gitSyncLogger,
|
||||
)
|
||||
@@ -2312,6 +2315,23 @@ type API struct {
|
||||
workspaceAgentConnWatcher *workspaceconnwatcher.Watcher
|
||||
}
|
||||
|
||||
// chatDaemonPublishDiffStatusChangeFunc returns chatDaemon's
|
||||
// PublishDiffStatusChange method bound as a gitsync.PublishDiffStatusChangeFunc,
|
||||
// or a true nil func value when chatDaemon is nil (AI Gateway disabled).
|
||||
//
|
||||
// This must not be inlined as chatDaemon.PublishDiffStatusChange: a method
|
||||
// value on a nil pointer receiver is itself non-nil (it captures the
|
||||
// receiver, it doesn't call the method), so gitsync.Worker's own "if
|
||||
// publishDiffStatusChangeFn != nil" check would not catch a nil chatDaemon,
|
||||
// and invoking the returned func would panic dereferencing the nil
|
||||
// receiver.
|
||||
func chatDaemonPublishDiffStatusChangeFunc(chatDaemon *chatd.Server) gitsync.PublishDiffStatusChangeFunc {
|
||||
if chatDaemon == nil {
|
||||
return nil
|
||||
}
|
||||
return chatDaemon.PublishDiffStatusChange
|
||||
}
|
||||
|
||||
// Close waits for all WebSocket connections to drain before returning.
|
||||
func (api *API) Close() error {
|
||||
select {
|
||||
@@ -2346,8 +2366,10 @@ func (api *API) Close() error {
|
||||
api.Logger.Warn(context.Background(),
|
||||
"chat diff refresh worker did not exit in time")
|
||||
}
|
||||
if err := api.chatDaemon.Close(); err != nil {
|
||||
api.Logger.Warn(api.ctx, "close chat processor", slog.Error(err))
|
||||
if api.chatDaemon != nil {
|
||||
if err := api.chatDaemon.Close(); err != nil {
|
||||
api.Logger.Warn(api.ctx, "close chat processor", slog.Error(err))
|
||||
}
|
||||
}
|
||||
api.metricsCache.Close()
|
||||
if api.updateChecker != nil {
|
||||
|
||||
Reference in New Issue
Block a user