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

Skip to content
Merged
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
3 changes: 2 additions & 1 deletion cli/aibridged_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
"github.com/coder/serpent"
)

Expand Down Expand Up @@ -53,7 +54,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, nil, logger, "/", cfg, nil, nil, agplaiseats.Noop{})
srv, err := aibridgedserver.NewServer(ctx, db, nil, logger, "/", cfg, nil, nil, agplaiseats.Noop{}, quartz.NewReal())
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 @@ -66,7 +66,7 @@ func (api *API) CreateInMemoryAIBridgeServer(dialCtx context.Context) (client ai

mux := drpcmux.New()
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)
api.AccessURL.String(), api.DeploymentValues.AI.BridgeConfig, api.ExternalAuthConfigs, api.Experiments, api.AISeatTracker, api.Clock)
if err != nil {
return nil, err
}
Expand Down
13 changes: 1 addition & 12 deletions coderd/aibridged/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@ import (

"github.com/google/uuid"
"golang.org/x/xerrors"
"google.golang.org/protobuf/types/known/timestamppb"

"cdr.dev/slog/v3"
"github.com/coder/coder/v2/aibridge"
"github.com/coder/coder/v2/aibridge/recorder"
agplaibridge "github.com/coder/coder/v2/coderd/aibridge"
"github.com/coder/coder/v2/coderd/aibridge/budget"
"github.com/coder/coder/v2/coderd/aibridged/proto"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/codersdk"
)

var _ http.Handler = &Server{}
Expand Down Expand Up @@ -149,15 +145,8 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
logger = logger.With(slog.F("user_id", id))

periodWindow, err := budget.CurrentPeriod(dbtime.Now(), codersdk.AIBudgetPeriodMonth)
if err != nil {
logger.Warn(ctx, "compute AI budget period", slog.Error(err))
http.Error(rw, ErrBudgetCheck.Error(), http.StatusInternalServerError)
return
}
budgetResp, err := client.IsBudgetExceeded(ctx, &proto.IsBudgetExceededRequest{
UserId: id.String(),
PeriodStart: timestamppb.New(periodWindow.Start),
UserId: id.String(),
})
if err != nil {
logger.Warn(ctx, "user AI budget check failed", slog.Error(err))
Expand Down
326 changes: 156 additions & 170 deletions coderd/aibridged/proto/aibridged.pb.go

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions coderd/aibridged/proto/aibridged.proto
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ service Authorizer {
// TODO: add authorization; currently only key validation takes place.
rpc IsAuthorized(IsAuthorizedRequest) returns (IsAuthorizedResponse);
// IsBudgetExceeded reports whether the user's AI spend has reached their
// effective limit over [period_start, now].
// effective limit for the current deployment-configured budget period.
rpc IsBudgetExceeded(IsBudgetExceededRequest) returns (IsBudgetExceededResponse);
}

Expand Down Expand Up @@ -189,8 +189,6 @@ message IsAuthorizedResponse {

message IsBudgetExceededRequest {
string user_id = 1; // UUID
// The spend aggregation window is [period_start, now].
google.protobuf.Timestamp period_start = 2;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parameter was incorrectly introduced in #26915: the period should have been derived from the deployment config from the beginning. Since this change was not part of any release, my understanding is that we can safely remove the field without marking it as deprecated. This would only be a problem if we had a server and a client running with different proto versions: an old server that still requires period_start receiving a request from a new client (which no longer sends it) would reject the request. Let me know if that is not the case.

}

message IsBudgetExceededResponse {
Expand Down
23 changes: 15 additions & 8 deletions coderd/aibridgedserver/aibridgedserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
coderdpubsub "github.com/coder/coder/v2/coderd/pubsub"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/quartz"
)

var (
Expand Down Expand Up @@ -113,11 +114,15 @@ type Server struct {
// budgetPolicy selects the effective group when a user belongs to multiple
// budgeted groups, used for cost attribution on token usage records.
budgetPolicy codersdk.AIBudgetPolicy
// budgetPeriod is the deployment-configured budgeting period used to
// derive the window over which user AI spend is aggregated.
budgetPeriod codersdk.AIBudgetPeriod
clock quartz.Clock
}

func NewServer(lifecycleCtx context.Context, store store, ps pubsub.Pubsub, logger slog.Logger, accessURL string,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the number of args is starting to get unwieldy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point 👍 This is a bigger change and touches a lot of files, including tests, so will address this in a follow-up PR.

bridgeCfg codersdk.AIBridgeConfig, externalAuthConfigs []*externalauth.Config, experiments codersdk.Experiments,
aiSeatTracker aiseats.SeatTracker,
aiSeatTracker aiseats.SeatTracker, clock quartz.Clock,
) (*Server, error) {
eac := make(map[string]*externalauth.Config, len(externalAuthConfigs))

Expand All @@ -138,6 +143,8 @@ func NewServer(lifecycleCtx context.Context, store store, ps pubsub.Pubsub, logg
structuredLogging: bridgeCfg.StructuredLogging.Value(),
aiSeatTracker: aiSeatTracker,
budgetPolicy: codersdk.NewAIBudgetPolicyFromString(bridgeCfg.BudgetPolicy),
budgetPeriod: codersdk.NewAIBudgetPeriodFromString(bridgeCfg.BudgetPeriod),
clock: clock,
}

if bridgeCfg.InjectCoderMCPTools {
Expand Down Expand Up @@ -747,7 +754,8 @@ func (s *Server) IsAuthorized(ctx context.Context, in *proto.IsAuthorizedRequest
}

// IsBudgetExceeded reports whether the user's AI spend has reached their
// effective limit over [PeriodStart, now].
// effective limit over [periodStart, now], where periodStart is the start of
// the current deployment-configured budget period.
func (s *Server) IsBudgetExceeded(ctx context.Context, in *proto.IsBudgetExceededRequest) (*proto.IsBudgetExceededResponse, error) {
//nolint:gocritic // AIBridged has specific authz rules.
ctx = dbauthz.AsAIBridged(ctx)
Expand All @@ -756,14 +764,13 @@ func (s *Server) IsBudgetExceeded(ctx context.Context, in *proto.IsBudgetExceede
if err != nil {
return nil, xerrors.Errorf("invalid user_id %q: %w", in.GetUserId(), err)
}
// An unset PeriodStart deserializes to time.Unix(0, 0), which would
// incorrectly aggregate the user's lifetime spend against a period budget.
if in.PeriodStart == nil {
return nil, xerrors.New("period_start is required")

periodWindow, err := budget.CurrentPeriod(s.clock.Now(), s.budgetPeriod)
if err != nil {
return nil, xerrors.Errorf("compute AI budget period: %w", err)
}
periodStart := in.GetPeriodStart().AsTime()

userBudget, err := s.checkUserAIBudget(ctx, userID, periodStart)
userBudget, err := s.checkUserAIBudget(ctx, userID, periodWindow.Start)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading