This commit is contained in:
2026-07-22 23:55:46 +02:00
parent 34f76aad65
commit 77f043640e
23 changed files with 993 additions and 11 deletions
@@ -117,4 +117,57 @@ GATEWAY_ACTIONS: tuple[Action, ...] = (
requires_admin=True,
params=(path("source_model", "Source model name."), confirm()),
),
Action(
name="gateway_quota_rules",
method="GET",
path="/admin/gateway/quota-rules",
summary="List AI gateway quota rules and the current global defaults (admin only)",
description=(
"Returns JSON: every quota rule (each scoped by any combination of role/owner_kind, "
"a specific user uid, and an app_reference label) with its 24h limit, current 24h spend "
"against that exact scope, active flag, and label, plus the global per-role default caps "
"used when no rule matches a request."
),
handler="http",
requires_admin=True,
read_only=True,
),
Action(
name="gateway_quota_rule_set",
method="POST",
path="/admin/gateway/quota-rules",
summary="Create or update an AI gateway quota rule (admin only)",
description=(
"Caps rolling-24h USD spend on /openai/v1/*. Scope by any combination of owner_kind "
"(internal/key/user/admin/anonymous), a specific owner_id (user uid), and app_reference "
"(the X-App-Reference header apps send). At least one of the three must be set - an "
"unscoped cap belongs in the global default fields on the gateway service config instead. "
"Leaving a dimension blank makes it a wildcard: an app_reference-only rule pools spend "
"across every caller using that app; an owner_kind-only rule pools spend across every "
"caller of that role. Setting owner_id pins the rule to one specific caller. When several "
"rules match one request, the MOST SPECIFIC one wins (most non-blank dimensions); ties "
"break toward the smaller limit. limit_usd of 0 means unlimited for that rule. Pass uid "
"to update an existing rule instead of creating a new one."
),
handler="http",
requires_admin=True,
params=(
body("uid", "Existing rule uid to update; omit to create a new rule."),
body("owner_kind", "Role to scope by: internal, key, user, admin, or anonymous. Blank = any role."),
body("owner_id", "Specific user uid to scope by. Blank = any caller of the matched role."),
body("app_reference", "App label to scope by (the X-App-Reference header). Blank = any app."),
Param(name="limit_usd", location="body", description="Rolling 24h USD cap for this rule. 0 = unlimited.", required=True, type="number"),
Param(name="is_active", location="body", description="Whether the rule is enforced ('1' or '0'). Defaults to active.", required=False, type="boolean"),
body("label", "Optional admin-facing note describing what this rule is for."),
),
),
Action(
name="gateway_quota_rule_delete",
method="DELETE",
path="/admin/gateway/quota-rules/{uid}",
summary="Delete an AI gateway quota rule (admin only, confirmation required)",
handler="http",
requires_admin=True,
params=(path("uid", "Quota rule uid."), confirm()),
),
)
@@ -58,6 +58,7 @@ CONFIRM_REQUIRED = {
"notification_reset",
"gateway_provider_delete",
"gateway_model_delete",
"gateway_quota_rule_delete",
"email_account_delete",
"email_delete_message",
}