Files
Danny Kopping 79e007cf30 feat: hot-reload aibridged and aibridgeproxyd providers on DB changes (#25673)
Previously the in-process aibridge daemon and the enterprise aibridgeproxy daemon both snapshotted their provider routing once at boot. Any `ai_providers` or `ai_provider_keys` mutation required a restart for either to pick it up.

Add an `ai_providers_changed` pubsub channel that the CRUD handlers publish on after Create / Update / Delete. Both daemons subscribe:

- **aibridged** rebuilds its `[]aibridge.Provider` snapshot via `BuildProviders` and swaps it into the pool atomically. Inflight requests keep serving against the bridge they already acquired; new acquires build against the new snapshot. Per-provider construction errors stay scoped to the offending row.
- **aibridgeproxyd** rebuilds its routing snapshot from `GetAIProviders` and swaps the host→provider map atomically. The MITM listener picks up new providers without restart.

DB read for aibridgeproxyd uses the existing `AsAIProviderMetadataReader` subject for routing-only access.
2026-05-27 11:58:43 +02:00

51 lines
1.4 KiB
Go

package aibridged
import (
"context"
"golang.org/x/xerrors"
"cdr.dev/slog/v3"
dbpubsub "github.com/coder/coder/v2/coderd/database/pubsub"
"github.com/coder/coder/v2/coderd/pubsub"
)
// ProviderReloader refreshes a component's provider snapshot.
type ProviderReloader interface {
Reload(ctx context.Context) error
}
// SubscribeProviderReload refreshes once, then on AI provider changes.
func SubscribeProviderReload(
ctx context.Context,
ps dbpubsub.Pubsub,
reloader ProviderReloader,
logger slog.Logger,
) (func(), error) {
if ps == nil {
return nil, xerrors.New("pubsub is required")
}
if reloader == nil {
return nil, xerrors.New("reloader is required")
}
unsubscribe, err := ps.SubscribeWithErr(pubsub.AIProvidersChangedChannel, func(cbCtx context.Context, _ []byte, err error) {
if err != nil {
logger.Warn(cbCtx, "ai providers changed event delivered with error", slog.Error(err))
return
}
if err := reloader.Reload(cbCtx); err != nil {
logger.Warn(cbCtx, "reload ai provider snapshot from pubsub event", slog.Error(err))
return
}
logger.Debug(cbCtx, "reloaded ai provider snapshot from pubsub event")
})
if err != nil {
return nil, xerrors.Errorf("subscribe to %s: %w", pubsub.AIProvidersChangedChannel, err)
}
if err := reloader.Reload(ctx); err != nil {
logger.Warn(ctx, "initial ai provider reload", slog.Error(err))
}
return unsubscribe, nil
}