|
# retoor <retoor@molodetz.nl>
|
|
|
|
import sys
|
|
|
|
from devplacepy.cli._shared import _audit_cli
|
|
|
|
|
|
def cmd_gateway_quota_list(args):
|
|
from devplacepy.services.openai_gateway import quota
|
|
|
|
rules = quota.quota_rule_store.list()
|
|
if not rules:
|
|
print("No quota rules. Every caller is capped by the global defaults on /admin/services/openai.")
|
|
return
|
|
for rule in rules:
|
|
spent = quota.spent_24h(rule["owner_kind"], rule["owner_id"], rule["app_reference"])
|
|
scope = ", ".join(
|
|
f"{key}={rule[key]}" for key in ("owner_kind", "owner_id", "app_reference") if rule[key]
|
|
) or "(no dimensions - invalid)"
|
|
limit = "unlimited" if rule["limit_usd"] == 0 else f"${rule['limit_usd']:.2f}/24h"
|
|
active = "active" if rule["is_active"] else "inactive"
|
|
label = f" - {rule['label']}" if rule["label"] else ""
|
|
print(f"{rule['uid']} [{scope}] {limit} spent=${spent:.4f} {active}{label}")
|
|
|
|
|
|
def cmd_gateway_quota_set(args):
|
|
from pydantic import ValidationError
|
|
from devplacepy.services.openai_gateway import quota
|
|
|
|
try:
|
|
payload = quota.QuotaRuleIn(
|
|
owner_kind=args.owner_kind,
|
|
owner_id=args.owner_id,
|
|
app_reference=args.app_reference,
|
|
limit_usd=args.limit_usd,
|
|
is_active=not args.inactive,
|
|
label=args.label or "",
|
|
)
|
|
except ValidationError as exc:
|
|
print(f"Invalid rule: {exc.errors()[0].get('msg', exc)}")
|
|
sys.exit(1)
|
|
saved = quota.quota_rule_store.set(payload, uid=args.uid, created_by="cli")
|
|
_audit_cli(
|
|
"gateway.quota_rule.update",
|
|
f"CLI saved gateway quota rule {saved['uid']}",
|
|
metadata={
|
|
"owner_kind": saved["owner_kind"],
|
|
"owner_id": saved["owner_id"],
|
|
"app_reference": saved["app_reference"],
|
|
"limit_usd": saved["limit_usd"],
|
|
},
|
|
target_type="gateway_quota_rule",
|
|
target_uid=saved["uid"],
|
|
)
|
|
print(f"Saved quota rule {saved['uid']}")
|
|
|
|
|
|
def cmd_gateway_quota_delete(args):
|
|
from devplacepy.services.openai_gateway import quota
|
|
|
|
if not quota.quota_rule_store.remove(args.uid):
|
|
print(f"Quota rule '{args.uid}' not found")
|
|
sys.exit(1)
|
|
_audit_cli(
|
|
"gateway.quota_rule.delete",
|
|
f"CLI deleted gateway quota rule {args.uid}",
|
|
target_type="gateway_quota_rule",
|
|
target_uid=args.uid,
|
|
)
|
|
print(f"Deleted quota rule {args.uid}")
|
|
|
|
|
|
def cmd_gateway_quota_reset(args):
|
|
from devplacepy.services.openai_gateway import quota
|
|
|
|
try:
|
|
payload = quota.QuotaResetIn(
|
|
owner_kind=args.owner_kind,
|
|
owner_id=args.owner_id,
|
|
app_reference=args.app_reference,
|
|
)
|
|
except Exception as exc:
|
|
print(f"Error: {exc}")
|
|
sys.exit(1)
|
|
scope = quota.reset(payload, created_by="cli")
|
|
label = quota.scope_label(scope, fallback="every caller")
|
|
_audit_cli(
|
|
"gateway.quota.reset",
|
|
f"CLI reset the gateway 24h spend for {label}",
|
|
target_type="gateway_quota",
|
|
target_uid=scope["uid"],
|
|
metadata={
|
|
"owner_kind": scope["owner_kind"],
|
|
"owner_id": scope["owner_id"],
|
|
"app_reference": scope["app_reference"],
|
|
"reset_at": scope["reset_at"],
|
|
},
|
|
)
|
|
print(f"Reset the rolling 24h spend for {label}")
|
|
|
|
|
|
def register_gateway(subparsers):
|
|
gateway = subparsers.add_parser("gateway", help="AI gateway management")
|
|
gateway_sub = gateway.add_subparsers(title="action", dest="action")
|
|
|
|
quota = gateway_sub.add_parser("quota", help="Manage rolling-24h AI gateway quota rules")
|
|
quota_sub = quota.add_subparsers(title="sub-action", dest="sub_action")
|
|
|
|
quota_list = quota_sub.add_parser("list", help="List all quota rules and their current 24h spend")
|
|
quota_list.set_defaults(func=cmd_gateway_quota_list)
|
|
|
|
quota_set = quota_sub.add_parser(
|
|
"set", help="Create or update a quota rule (scope by role/user/app, any combination)"
|
|
)
|
|
quota_set.add_argument("--uid", help="Existing rule uid to update; omit to create a new rule")
|
|
quota_set.add_argument(
|
|
"--owner-kind",
|
|
choices=("internal", "key", "user", "admin", "anonymous"),
|
|
help="Role to scope by. Omit for any role",
|
|
)
|
|
quota_set.add_argument("--owner-id", help="Specific user uid to scope by. Omit for any caller")
|
|
quota_set.add_argument("--app-reference", help="App label to scope by. Omit for any app")
|
|
quota_set.add_argument(
|
|
"--limit-usd", type=float, required=True, help="Rolling 24h USD cap (0 = unlimited)"
|
|
)
|
|
quota_set.add_argument("--label", help="Optional admin-facing note")
|
|
quota_set.add_argument("--inactive", action="store_true", help="Create the rule disabled")
|
|
quota_set.set_defaults(func=cmd_gateway_quota_set)
|
|
|
|
quota_delete = quota_sub.add_parser("delete", help="Delete a quota rule by uid")
|
|
quota_delete.add_argument("uid", help="Quota rule uid")
|
|
quota_delete.set_defaults(func=cmd_gateway_quota_delete)
|
|
|
|
quota_reset = quota_sub.add_parser(
|
|
"reset",
|
|
help="Clear the rolling-24h spend so a capped caller can call again (keeps the usage history)",
|
|
)
|
|
quota_reset.add_argument(
|
|
"--owner-kind",
|
|
choices=("internal", "key", "user", "admin", "anonymous"),
|
|
help="Role to scope by. Omit to reset every role",
|
|
)
|
|
quota_reset.add_argument("--owner-id", help="Specific user uid to scope by. Omit for every caller")
|
|
quota_reset.add_argument("--app-reference", help="App label to scope by. Omit for every app")
|
|
quota_reset.set_defaults(func=cmd_gateway_quota_reset)
|