mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: remove ai-gateway-cost-control experiment flag (#27579)
## Description Closes [AIGOV-443](https://linear.app/codercom/issue/AIGOV-443/remove-ai-gateway-cost-control-experiment-flag-once-feature-is-stable). The AI Gateway cost control feature is planned for GA on the upcoming release, so this removes the `ExperimentAIGatewayCostControl` experiment and all of its gating. The cost control API endpoints remain gated by the `FeatureAIBridge` license feature (the AI Governance add-on), so this only drops the experiment layer. ## Changes - **`codersdk/deployment.go`**: remove the `ExperimentAIGatewayCostControl` const, its `DisplayName()` case, and its `ExperimentsKnown` entry. - **`enterprise/coderd/coderd.go`**: remove the `httpmw.RequireExperiment(...)` gating from the AI cost control routes. They keep `RequireFeatureMW(codersdk.FeatureAIBridge)`. Affected endpoints: - `GET /organizations/{organization}/groups/ai/spend` - `GET /organizations/{organization}/groups/{groupName}/members/ai/spend` - `GET /organizations/{organization}/ai/spend/export` - `GET /groups/{group}/members/ai/spend` - `GET /groups/{group}/ai/spend` - `GET/PUT/DELETE /users/{user}/ai/budget/override` and `GET /users/{user}/ai/spend` - **`enterprise/coderd/aibridge_test.go`**: drop the experiment from test setup and remove the now-obsolete `RequiresExperiment` negative-path tests. - **Frontend (`site/src/...`)**: remove the `ai-gateway-cost-control` experiment checks from the cost control UI (Groups pages, user dropdown) and their stories/mocks. The feature is now driven solely by the `aibridge` feature visibility. - **Generated**: regenerated `coderd/apidoc/*`, `docs/reference/api/schemas.md`, and `site/src/api/typesGenerated.ts`. ## Out of scope The dogfood `CODER_EXPERIMENTS` config lives in a separate infra repo, not `coder/coder`. Leaving `ai-gateway-cost-control` there is harmless: unknown experiment values are logged as `"ignoring unknown experiment"` at startup and otherwise ignored, so no ordering dependency or breakage. That cleanup can be a follow-up. <details> <summary>Implementation notes</summary> - Verified how unknown experiments are handled in `coderd/coderd.go` `ReadExperiments`: unknown values produce a warning log and are inert, so removing the definition before the dogfood config is updated is safe. - Noticed the group `ai/budget` routes (`/groups/{group}/ai/budget`) were already gated only by `FeatureAIBridge`, never by the experiment. After this change all cost control routes are uniformly feature-gated, resolving that inconsistency. - Removed an obsolete `RequiresExperiment` subtest in `TestUserAISpendStatus` that only asserted a 403 from the experiment gate; with the gate gone it would no longer be blocked pre-RBAC. </details> --- _This PR was created by Coder Agents on behalf of @ssncferreira._
This commit is contained in:
@@ -2998,7 +2998,6 @@ func TestUserAIBudgetOverrideRoleAccess(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -3096,7 +3095,6 @@ func TestUserAIBudgetOverrideDeletedOnMembershipRemoval(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -3186,7 +3184,6 @@ func TestUserAISpendStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -3202,28 +3199,6 @@ func TestUserAISpendStatus(t *testing.T) {
|
||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("RequiresExperiment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
//nolint:gocritic // Owner role is irrelevant here; the request is blocked before RBAC.
|
||||
_, err := client.UserAISpendStatus(ctx, uuid.New())
|
||||
var sdkErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &sdkErr)
|
||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
groupBudget *int64 // nil = no group budget configured
|
||||
@@ -3421,7 +3396,6 @@ func TestUserAISpendStatusRoleAccess(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -3475,7 +3449,6 @@ func TestOrganizationGroupsAISpend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
client, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -3494,30 +3467,6 @@ func TestOrganizationGroupsAISpend(t *testing.T) {
|
||||
require.Contains(t, sdkErr.Message, "AI Gateway is a Premium feature")
|
||||
})
|
||||
|
||||
t.Run("RequiresExperiment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureTemplateRBAC: 1,
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
//nolint:gocritic // Owner role is irrelevant here; the request is blocked before RBAC.
|
||||
_, err := client.OrganizationGroupsAISpend(ctx, owner.OrganizationID, []uuid.UUID{uuid.New()})
|
||||
var sdkErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &sdkErr)
|
||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||
require.Contains(t, sdkErr.Message, "ai-gateway-cost-control")
|
||||
})
|
||||
|
||||
t.Run("MissingGroupIDs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -3754,7 +3703,6 @@ func TestOrganizationGroupsAISpendRoleAccess(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -3857,22 +3805,14 @@ func TestExportOrganizationAISpend(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
experiments []string
|
||||
features license.Features
|
||||
wantMsgContains string
|
||||
}{
|
||||
{
|
||||
name: "RequiresLicenseFeature",
|
||||
experiments: []string{string(codersdk.ExperimentAIGatewayCostControl)},
|
||||
features: license.Features{},
|
||||
wantMsgContains: "AI Gateway is a Premium feature",
|
||||
},
|
||||
{
|
||||
name: "RequiresExperiment",
|
||||
experiments: nil,
|
||||
features: license.Features{codersdk.FeatureAIBridge: 1},
|
||||
wantMsgContains: "ai-gateway-cost-control",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -3880,9 +3820,6 @@ func TestExportOrganizationAISpend(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
if len(tc.experiments) > 0 {
|
||||
dv.Experiments = tc.experiments
|
||||
}
|
||||
client, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{Features: tc.features},
|
||||
@@ -4525,7 +4462,6 @@ func TestExportOrganizationAISpend(t *testing.T) {
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv, Database: db, Pubsub: ps, Clock: clock},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -4690,7 +4626,6 @@ func TestGroupAISpend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -4713,34 +4648,6 @@ func TestGroupAISpend(t *testing.T) {
|
||||
require.Contains(t, sdkErr.Message, "AI Gateway is a Premium feature")
|
||||
})
|
||||
|
||||
t.Run("RequiresExperiment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureTemplateRBAC: 1,
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
adminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleUserAdmin())
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
group, err := adminClient.CreateGroup(ctx, owner.OrganizationID, codersdk.CreateGroupRequest{
|
||||
Name: "req-experiment-spend-group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = adminClient.GroupAISpend(ctx, group.ID)
|
||||
var sdkErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &sdkErr)
|
||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||
require.Contains(t, sdkErr.Message, "ai-gateway-cost-control")
|
||||
})
|
||||
|
||||
t.Run("MalformedGroupID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -4904,7 +4811,6 @@ func TestGroupAISpendRoleAccess(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -4975,7 +4881,6 @@ func TestExportOrganizationAISpendRoleAccess(t *testing.T) {
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv, Database: db, Pubsub: ps, Clock: clock},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -5067,7 +4972,6 @@ func TestGroupMembersAISpend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -5090,34 +4994,6 @@ func TestGroupMembersAISpend(t *testing.T) {
|
||||
require.Contains(t, sdkErr.Message, "AI Gateway is a Premium feature")
|
||||
})
|
||||
|
||||
t.Run("RequiresExperiment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureTemplateRBAC: 1,
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
adminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleUserAdmin())
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
group, err := adminClient.CreateGroup(ctx, owner.OrganizationID, codersdk.CreateGroupRequest{
|
||||
Name: "req-experiment-members-group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = adminClient.GroupMembersAISpend(ctx, group.ID, []uuid.UUID{uuid.New()})
|
||||
var sdkErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &sdkErr)
|
||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||
require.Contains(t, sdkErr.Message, "ai-gateway-cost-control")
|
||||
})
|
||||
|
||||
t.Run("MissingUserIDs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -5347,7 +5223,6 @@ func TestGroupMembersAISpend(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv, Database: db, Pubsub: ps},
|
||||
@@ -5406,7 +5281,6 @@ func TestGroupMembersAISpend(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv, Database: db, Pubsub: ps},
|
||||
@@ -5496,7 +5370,6 @@ func TestGroupMembersAISpendRoleAccess(t *testing.T) {
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{DeploymentValues: dv},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
@@ -5586,16 +5459,14 @@ type aiCostControlTestOptions struct {
|
||||
Retention *time.Duration
|
||||
}
|
||||
|
||||
// setupAICostControlTest builds a deployment with FeatureAIBridge licensed
|
||||
// and the AI Gateway cost control experiment enabled, creates an admin
|
||||
// client and target user, adds the target user to a group, and returns
|
||||
// the admin client, target user, and group.
|
||||
// setupAICostControlTest builds a deployment with FeatureAIBridge licensed,
|
||||
// creates an admin client and target user, adds the target user to a group,
|
||||
// and returns the admin client, target user, and group.
|
||||
func setupAICostControlTest(t *testing.T, opts aiCostControlTestOptions) (*codersdk.Client, codersdk.User, codersdk.Group) {
|
||||
t.Helper()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
if opts.Retention != nil {
|
||||
dv.AI.BridgeConfig.Retention = serpent.Duration(*opts.Retention)
|
||||
}
|
||||
@@ -5647,7 +5518,6 @@ func setupUserAIBudgetOverrideAuditTest(t *testing.T) (database.Store, *codersdk
|
||||
)
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAIGatewayCostControl)}
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
AuditLogging: true,
|
||||
Options: &coderdtest.Options{
|
||||
|
||||
@@ -506,8 +506,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Route("/ai/spend", func(r chi.Router) {
|
||||
// AI cost controls are a paid feature (AI Governance add-on).
|
||||
r.Use(
|
||||
// TODO(AIGOV-443): remove once AI Gateway cost control functionality is stable.
|
||||
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentAIGatewayCostControl),
|
||||
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
||||
)
|
||||
r.Get("/", api.organizationGroupsAISpend)
|
||||
@@ -522,8 +520,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Route("/members/ai/spend", func(r chi.Router) {
|
||||
// AI cost controls are a paid feature (AI Governance add-on).
|
||||
r.Use(
|
||||
// TODO(AIGOV-443): remove once AI Gateway cost control functionality is stable.
|
||||
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentAIGatewayCostControl),
|
||||
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
||||
)
|
||||
r.Get("/", api.groupMembersAISpendByOrganization)
|
||||
@@ -535,8 +531,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractOrganizationParam(api.Database),
|
||||
// TODO(AIGOV-443): remove once AI Gateway cost control functionality is stable.
|
||||
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentAIGatewayCostControl),
|
||||
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
||||
)
|
||||
r.Get("/export", api.exportOrganizationAISpend)
|
||||
@@ -627,8 +621,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Route("/members/ai/spend", func(r chi.Router) {
|
||||
// AI cost controls are a paid feature (AI Governance add-on).
|
||||
r.Use(
|
||||
// TODO(AIGOV-443): remove once AI Gateway cost control functionality is stable.
|
||||
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentAIGatewayCostControl),
|
||||
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
||||
)
|
||||
r.Get("/", api.groupMembersAISpend)
|
||||
@@ -636,8 +628,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Route("/ai/spend", func(r chi.Router) {
|
||||
// AI cost controls are a paid feature (AI Governance add-on).
|
||||
r.Use(
|
||||
// TODO(AIGOV-443): remove once AI Gateway cost control functionality is stable.
|
||||
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentAIGatewayCostControl),
|
||||
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
||||
)
|
||||
r.Get("/", api.groupAISpend)
|
||||
@@ -692,8 +682,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Route("/users/{user}/ai", func(r chi.Router) {
|
||||
// AI cost controls are a paid feature (AI Governance add-on).
|
||||
r.Use(
|
||||
// TODO(AIGOV-443): remove once AI Gateway cost control functionality is stable.
|
||||
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentAIGatewayCostControl),
|
||||
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractUserParam(options.Database),
|
||||
|
||||
Reference in New Issue
Block a user