Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/aibridged_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func buildFromEnv(t *testing.T, cfg codersdk.AIBridgeConfig) ([]aibridge.Provide
// (providers, outcomes) the embedded reloader would observe.
func buildFromDB(ctx context.Context, t *testing.T, db database.Store, cfg codersdk.AIBridgeConfig, logger slog.Logger) ([]aibridge.Provider, []aibridged.ProviderOutcome, error) {
t.Helper()
srv, err := aibridgedserver.NewServer(ctx, db, logger, "/", cfg, nil, nil, agplaiseats.Noop{})
srv, err := aibridgedserver.NewServer(ctx, db, nil, logger, "/", cfg, nil, nil, agplaiseats.Noop{})
if err != nil {
return nil, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion coderd/aibridged.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (api *API) CreateInMemoryAIBridgeServer(dialCtx context.Context) (client ai
}()

mux := drpcmux.New()
srv, err := aibridgedserver.NewServer(api.ctx, api.Database, api.Logger.Named("aibridgedserver"),
srv, err := aibridgedserver.NewServer(api.ctx, api.Database, api.Pubsub, api.Logger.Named("aibridgedserver"),
api.AccessURL.String(), api.DeploymentValues.AI.BridgeConfig, api.ExternalAuthConfigs, api.Experiments, api.AISeatTracker)
if err != nil {
return nil, err
Expand Down
15 changes: 15 additions & 0 deletions coderd/aibridged/aibridgedmock/clientmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

397 changes: 256 additions & 141 deletions coderd/aibridged/proto/aibridged.pb.go

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions coderd/aibridged/proto/aibridged.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ service ProviderConfigurator {
// GetAIProviders returns the full provider set (enabled and disabled).
// It synchronizes with provider seeding so the response is never raced.
rpc GetAIProviders(GetAIProvidersRequest) returns (GetAIProvidersResponse);
// WatchAIProviders streams a signal whenever the provider set changes
// (env seed completion or a CRUD add/update/delete). The signal carries no
// payload; on each message the client refetches via GetAIProviders. The
// server emits one signal immediately on subscribe so a client that
// connected after the last change still converges.
rpc WatchAIProviders(WatchAIProvidersRequest) returns (stream WatchAIProvidersResponse);
}

message RecordInterceptionRequest {
Expand Down Expand Up @@ -174,6 +180,12 @@ message GetAIProvidersResponse {
repeated AIProvider providers = 1;
}

message WatchAIProvidersRequest {}

// WatchAIProvidersResponse is an intentionally empty change signal. The client
// refetches the authoritative provider set via GetAIProviders on receipt.
message WatchAIProvidersResponse {}

message AIProvider {
string name = 1;
string type = 2;
Expand Down
70 changes: 69 additions & 1 deletion coderd/aibridged/proto/aibridged_drpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion coderd/aibridged/proto/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ import "github.com/coder/coder/v2/apiversion"
// - Adds the ProviderConfigurator service with the GetAIProviders unary RPC,
// letting embedded and standalone gateways fetch provider configuration
// over DRPC instead of reading the database directly.
//
// API v1.2:
// - Adds the ProviderConfigurator.WatchAIProviders streaming RPC, pushing a
// change signal to gateways so a running standalone gateway refetches its
// provider set when the provider configuration changes.
const (
CurrentMajor = 1
CurrentMinor = 1
CurrentMinor = 2
)

// CurrentVersion is the current aibridged API version.
Expand Down
68 changes: 68 additions & 0 deletions coderd/aibridged/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package aibridged

import (
"context"
"time"

"golang.org/x/xerrors"

"cdr.dev/slog/v3"
"github.com/coder/coder/v2/coderd/aibridged/proto"
dbpubsub "github.com/coder/coder/v2/coderd/database/pubsub"
"github.com/coder/coder/v2/coderd/pubsub"
"github.com/coder/retry"
)

// ProviderReloader refreshes a component's provider snapshot.
Expand Down Expand Up @@ -55,3 +58,68 @@ func SubscribeProviderReload(
}
return unsubscribe, nil
}

// WatchProviderReload opens a coderd WatchAIProviders stream via client and
// calls reloader.Reload on each change signal the server emits. The stream is
// re-established with exponential backoff whenever it drops. It runs until ctx
// is canceled, then returns ctx.Err(). It does not perform an initial load; the
// caller is responsible for any blocking load before serving.
func WatchProviderReload(
ctx context.Context,
client ClientFunc,
reloader ProviderReloader,
logger slog.Logger,
) error {
if client == nil {
return xerrors.New("client is required")
}
if reloader == nil {
return xerrors.New("reloader is required")
}

r := retry.New(50*time.Millisecond, 10*time.Second)
for {
connected, err := watchProviderReloadOnce(ctx, client, reloader, logger)
if ctx.Err() != nil {
return ctx.Err()
}
logger.Warn(ctx, "ai provider watch stream ended; reconnecting", slog.Error(err))
// A stream that opened resets the backoff so the next reconnect starts
// from the floor.
if connected {
r.Reset()
}
if !r.Wait(ctx) {
return ctx.Err()
}
}
}

// watchProviderReloadOnce opens a single WatchAIProviders stream and reloads on
// each signal until the stream fails. connected reports whether the stream
// opened before the error.
func watchProviderReloadOnce(ctx context.Context, client ClientFunc, reloader ProviderReloader, logger slog.Logger) (connected bool, err error) {
// client() blocks until the daemon is connected to coderd.
c, err := client()
if err != nil {
return false, xerrors.Errorf("get ai-gateway client: %w", err)
}
stream, err := c.WatchAIProviders(ctx, &proto.WatchAIProvidersRequest{})
if err != nil {
return false, xerrors.Errorf("open ai providers watch stream: %w", err)
}
defer func() {
_ = stream.Close()
}()

for {
if _, err := stream.Recv(); err != nil {
return true, xerrors.Errorf("receive ai providers change signal: %w", err)
}
if err := reloader.Reload(ctx); err != nil {
logger.Warn(ctx, "failed to reload ai provider snapshot from watch signal", slog.Error(err))
continue
}
logger.Debug(ctx, "reloaded ai provider snapshot from watch signal")
}
}
Loading
Loading