Spaces:
Paused
Paused
Mirrowel commited on
Commit ·
1af1879
1
Parent(s): 1ac7bd0
refactor(quota-viewer): 🔨 enhance credential sorting and cooldown display
Browse filesAdd natural/numeric sorting for credentials to ensure proper ordering
(e.g., proj-1, proj-2, proj-10 instead of proj-1, proj-10, proj-2).
Improve cooldown display in quota viewer by grouping cooldowns by quota
groups when available, providing clearer visibility into rate limiting
status for grouped models.
Also in this commit:
- refactor(client): improve model stats lookup with alias support
- feat(usage-manager): add quota display formatting for logging
- src/proxy_app/quota_viewer.py +82 -13
- src/rotator_library/client.py +98 -63
- src/rotator_library/usage_manager.py +51 -4
src/proxy_app/quota_viewer.py
CHANGED
|
@@ -6,6 +6,7 @@ Uses only httpx + rich (no heavy rotator_library imports).
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import os
|
|
|
|
| 9 |
import sys
|
| 10 |
import time
|
| 11 |
from datetime import datetime, timezone
|
|
@@ -128,6 +129,19 @@ def format_cooldown(seconds: int) -> str:
|
|
| 128 |
return f"{hours}h {mins}m" if mins > 0 else f"{hours}h"
|
| 129 |
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
class QuotaViewer:
|
| 132 |
"""Main Quota Viewer TUI class."""
|
| 133 |
|
|
@@ -548,6 +562,9 @@ class QuotaViewer:
|
|
| 548 |
prov_stats = self.cached_stats.get("providers", {}).get(provider, {})
|
| 549 |
credentials = prov_stats.get("credentials", [])
|
| 550 |
|
|
|
|
|
|
|
|
|
|
| 551 |
if not credentials:
|
| 552 |
self.console.print(
|
| 553 |
"[dim]No credentials configured for this provider.[/dim]"
|
|
@@ -584,6 +601,8 @@ class QuotaViewer:
|
|
| 584 |
if self.cached_stats
|
| 585 |
else []
|
| 586 |
)
|
|
|
|
|
|
|
| 587 |
for idx, cred in enumerate(credentials, 1):
|
| 588 |
identifier = cred.get("identifier", f"credential {idx}")
|
| 589 |
email = cred.get("email", identifier)
|
|
@@ -640,6 +659,8 @@ class QuotaViewer:
|
|
| 640 |
if self.cached_stats
|
| 641 |
else []
|
| 642 |
)
|
|
|
|
|
|
|
| 643 |
if 1 <= idx <= len(credentials):
|
| 644 |
cred = credentials[idx - 1]
|
| 645 |
cred_id = cred.get("identifier", "")
|
|
@@ -717,21 +738,69 @@ class QuotaViewer:
|
|
| 717 |
f"[dim]{stats_line}[/dim]",
|
| 718 |
]
|
| 719 |
|
| 720 |
-
# Show model cooldowns if any
|
| 721 |
-
if model_cooldowns:
|
| 722 |
-
content_lines.append("")
|
| 723 |
-
content_lines.append("[yellow]Active Cooldowns:[/yellow]")
|
| 724 |
-
for model_name, cooldown_info in model_cooldowns.items():
|
| 725 |
-
remaining = cooldown_info.get("remaining_seconds", 0)
|
| 726 |
-
if remaining > 0:
|
| 727 |
-
# Shorten model name for display
|
| 728 |
-
short_model = model_name.split("/")[-1][:35]
|
| 729 |
-
content_lines.append(
|
| 730 |
-
f" [yellow]⏱️ {short_model}: {format_cooldown(int(remaining))}[/yellow]"
|
| 731 |
-
)
|
| 732 |
-
|
| 733 |
# Model groups (for providers with quota tracking)
|
| 734 |
model_groups = cred.get("model_groups", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 735 |
if model_groups:
|
| 736 |
content_lines.append("")
|
| 737 |
for group_name, group_stats in model_groups.items():
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import os
|
| 9 |
+
import re
|
| 10 |
import sys
|
| 11 |
import time
|
| 12 |
from datetime import datetime, timezone
|
|
|
|
| 129 |
return f"{hours}h {mins}m" if mins > 0 else f"{hours}h"
|
| 130 |
|
| 131 |
|
| 132 |
+
def natural_sort_key(item: Dict[str, Any]) -> List:
|
| 133 |
+
"""
|
| 134 |
+
Generate a sort key for natural/numeric sorting.
|
| 135 |
+
|
| 136 |
+
Sorts credentials like proj-1, proj-2, proj-10 correctly
|
| 137 |
+
instead of alphabetically (proj-1, proj-10, proj-2).
|
| 138 |
+
"""
|
| 139 |
+
identifier = item.get("identifier", "")
|
| 140 |
+
# Split into text and numeric parts
|
| 141 |
+
parts = re.split(r"(\d+)", identifier)
|
| 142 |
+
return [int(p) if p.isdigit() else p.lower() for p in parts]
|
| 143 |
+
|
| 144 |
+
|
| 145 |
class QuotaViewer:
|
| 146 |
"""Main Quota Viewer TUI class."""
|
| 147 |
|
|
|
|
| 562 |
prov_stats = self.cached_stats.get("providers", {}).get(provider, {})
|
| 563 |
credentials = prov_stats.get("credentials", [])
|
| 564 |
|
| 565 |
+
# Sort credentials naturally (1, 2, 10 not 1, 10, 2)
|
| 566 |
+
credentials = sorted(credentials, key=natural_sort_key)
|
| 567 |
+
|
| 568 |
if not credentials:
|
| 569 |
self.console.print(
|
| 570 |
"[dim]No credentials configured for this provider.[/dim]"
|
|
|
|
| 601 |
if self.cached_stats
|
| 602 |
else []
|
| 603 |
)
|
| 604 |
+
# Sort credentials naturally
|
| 605 |
+
credentials = sorted(credentials, key=natural_sort_key)
|
| 606 |
for idx, cred in enumerate(credentials, 1):
|
| 607 |
identifier = cred.get("identifier", f"credential {idx}")
|
| 608 |
email = cred.get("email", identifier)
|
|
|
|
| 659 |
if self.cached_stats
|
| 660 |
else []
|
| 661 |
)
|
| 662 |
+
# Sort credentials naturally to match display order
|
| 663 |
+
credentials = sorted(credentials, key=natural_sort_key)
|
| 664 |
if 1 <= idx <= len(credentials):
|
| 665 |
cred = credentials[idx - 1]
|
| 666 |
cred_id = cred.get("identifier", "")
|
|
|
|
| 738 |
f"[dim]{stats_line}[/dim]",
|
| 739 |
]
|
| 740 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 741 |
# Model groups (for providers with quota tracking)
|
| 742 |
model_groups = cred.get("model_groups", {})
|
| 743 |
+
|
| 744 |
+
# Show cooldowns grouped by quota group (if model_groups exist)
|
| 745 |
+
if model_cooldowns:
|
| 746 |
+
if model_groups:
|
| 747 |
+
# Group cooldowns by quota group
|
| 748 |
+
group_cooldowns: Dict[
|
| 749 |
+
str, int
|
| 750 |
+
] = {} # group_name -> max_remaining_seconds
|
| 751 |
+
ungrouped_cooldowns: List[Tuple[str, int]] = []
|
| 752 |
+
|
| 753 |
+
for model_name, cooldown_info in model_cooldowns.items():
|
| 754 |
+
remaining = cooldown_info.get("remaining_seconds", 0)
|
| 755 |
+
if remaining <= 0:
|
| 756 |
+
continue
|
| 757 |
+
|
| 758 |
+
# Find which group this model belongs to
|
| 759 |
+
clean_model = model_name.split("/")[-1]
|
| 760 |
+
found_group = None
|
| 761 |
+
for group_name, group_info in model_groups.items():
|
| 762 |
+
group_models = group_info.get("models", [])
|
| 763 |
+
if clean_model in group_models:
|
| 764 |
+
found_group = group_name
|
| 765 |
+
break
|
| 766 |
+
|
| 767 |
+
if found_group:
|
| 768 |
+
group_cooldowns[found_group] = max(
|
| 769 |
+
group_cooldowns.get(found_group, 0), remaining
|
| 770 |
+
)
|
| 771 |
+
else:
|
| 772 |
+
ungrouped_cooldowns.append((model_name, remaining))
|
| 773 |
+
|
| 774 |
+
if group_cooldowns or ungrouped_cooldowns:
|
| 775 |
+
content_lines.append("")
|
| 776 |
+
content_lines.append("[yellow]Active Cooldowns:[/yellow]")
|
| 777 |
+
|
| 778 |
+
# Show grouped cooldowns
|
| 779 |
+
for group_name in sorted(group_cooldowns.keys()):
|
| 780 |
+
remaining = group_cooldowns[group_name]
|
| 781 |
+
content_lines.append(
|
| 782 |
+
f" [yellow]⏱️ {group_name}: {format_cooldown(remaining)}[/yellow]"
|
| 783 |
+
)
|
| 784 |
+
|
| 785 |
+
# Show ungrouped (shouldn't happen often)
|
| 786 |
+
for model_name, remaining in ungrouped_cooldowns:
|
| 787 |
+
short_model = model_name.split("/")[-1][:35]
|
| 788 |
+
content_lines.append(
|
| 789 |
+
f" [yellow]⏱️ {short_model}: {format_cooldown(remaining)}[/yellow]"
|
| 790 |
+
)
|
| 791 |
+
else:
|
| 792 |
+
# No model groups - show per-model cooldowns
|
| 793 |
+
content_lines.append("")
|
| 794 |
+
content_lines.append("[yellow]Active Cooldowns:[/yellow]")
|
| 795 |
+
for model_name, cooldown_info in model_cooldowns.items():
|
| 796 |
+
remaining = cooldown_info.get("remaining_seconds", 0)
|
| 797 |
+
if remaining > 0:
|
| 798 |
+
short_model = model_name.split("/")[-1][:35]
|
| 799 |
+
content_lines.append(
|
| 800 |
+
f" [yellow]⏱️ {short_model}: {format_cooldown(int(remaining))}[/yellow]"
|
| 801 |
+
)
|
| 802 |
+
|
| 803 |
+
# Display model groups with quota info
|
| 804 |
if model_groups:
|
| 805 |
content_lines.append("")
|
| 806 |
for group_name, group_stats in model_groups.items():
|
src/rotator_library/client.py
CHANGED
|
@@ -2664,27 +2664,25 @@ class RotatingClient:
|
|
| 2664 |
models_data = cred.get("models", {})
|
| 2665 |
group_stats["credentials_total"] += 1
|
| 2666 |
|
| 2667 |
-
# Find any model from this group
|
|
|
|
| 2668 |
for model in group_models:
|
| 2669 |
-
|
| 2670 |
-
|
| 2671 |
-
|
| 2672 |
-
prefixed_model
|
| 2673 |
-
) or models_data.get(model)
|
| 2674 |
-
|
| 2675 |
if model_stats:
|
| 2676 |
-
baseline = model_stats.get(
|
| 2677 |
-
"baseline_remaining_fraction"
|
| 2678 |
-
)
|
| 2679 |
-
if baseline is not None:
|
| 2680 |
-
remaining_pct = int(baseline * 100)
|
| 2681 |
-
group_stats["total_remaining_pcts"].append(
|
| 2682 |
-
remaining_pct
|
| 2683 |
-
)
|
| 2684 |
-
if baseline <= 0:
|
| 2685 |
-
group_stats["credentials_exhausted"] += 1
|
| 2686 |
break
|
| 2687 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2688 |
# Calculate average remaining percentage
|
| 2689 |
if group_stats["total_remaining_pcts"]:
|
| 2690 |
group_stats["avg_remaining_pct"] = int(
|
|
@@ -2701,56 +2699,53 @@ class RotatingClient:
|
|
| 2701 |
models_data = cred.get("models", {})
|
| 2702 |
|
| 2703 |
for group_name, group_models in quota_groups.items():
|
| 2704 |
-
# Find representative model from this group
|
|
|
|
| 2705 |
for model in group_models:
|
| 2706 |
-
|
| 2707 |
-
|
| 2708 |
-
|
| 2709 |
-
) or models_data.get(model)
|
| 2710 |
-
|
| 2711 |
if model_stats:
|
| 2712 |
-
baseline = model_stats.get(
|
| 2713 |
-
"baseline_remaining_fraction"
|
| 2714 |
-
)
|
| 2715 |
-
max_req = model_stats.get("quota_max_requests")
|
| 2716 |
-
req_count = model_stats.get("request_count", 0)
|
| 2717 |
-
reset_ts = model_stats.get("quota_reset_ts")
|
| 2718 |
-
|
| 2719 |
-
remaining_pct = (
|
| 2720 |
-
int(baseline * 100)
|
| 2721 |
-
if baseline is not None
|
| 2722 |
-
else None
|
| 2723 |
-
)
|
| 2724 |
-
is_exhausted = baseline is not None and baseline <= 0
|
| 2725 |
-
|
| 2726 |
-
# Format reset time
|
| 2727 |
-
reset_iso = None
|
| 2728 |
-
if reset_ts:
|
| 2729 |
-
try:
|
| 2730 |
-
from datetime import datetime, timezone
|
| 2731 |
-
|
| 2732 |
-
reset_iso = datetime.fromtimestamp(
|
| 2733 |
-
reset_ts, tz=timezone.utc
|
| 2734 |
-
).isoformat()
|
| 2735 |
-
except (ValueError, OSError):
|
| 2736 |
-
pass
|
| 2737 |
-
|
| 2738 |
-
cred["model_groups"][group_name] = {
|
| 2739 |
-
"remaining_pct": remaining_pct,
|
| 2740 |
-
"requests_used": req_count,
|
| 2741 |
-
"requests_max": max_req,
|
| 2742 |
-
"display": f"{req_count}/{max_req}"
|
| 2743 |
-
if max_req
|
| 2744 |
-
else f"{req_count}/?",
|
| 2745 |
-
"is_exhausted": is_exhausted,
|
| 2746 |
-
"reset_time_iso": reset_iso,
|
| 2747 |
-
"models": group_models,
|
| 2748 |
-
"confidence": self._get_baseline_confidence(
|
| 2749 |
-
model_stats
|
| 2750 |
-
),
|
| 2751 |
-
}
|
| 2752 |
break
|
| 2753 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2754 |
# Try to get email from provider's cache
|
| 2755 |
cred_path = cred.get("full_path", "")
|
| 2756 |
if hasattr(provider_instance, "project_tier_cache"):
|
|
@@ -2760,6 +2755,46 @@ class RotatingClient:
|
|
| 2760 |
|
| 2761 |
return stats
|
| 2762 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2763 |
def _get_baseline_confidence(self, model_stats: Dict) -> str:
|
| 2764 |
"""
|
| 2765 |
Determine confidence level based on baseline age.
|
|
|
|
| 2664 |
models_data = cred.get("models", {})
|
| 2665 |
group_stats["credentials_total"] += 1
|
| 2666 |
|
| 2667 |
+
# Find any model from this group (try all with alias fallback)
|
| 2668 |
+
model_stats = None
|
| 2669 |
for model in group_models:
|
| 2670 |
+
model_stats = self._find_model_stats_in_data(
|
| 2671 |
+
models_data, model, provider, provider_instance
|
| 2672 |
+
)
|
|
|
|
|
|
|
|
|
|
| 2673 |
if model_stats:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2674 |
break
|
| 2675 |
|
| 2676 |
+
if model_stats:
|
| 2677 |
+
baseline = model_stats.get("baseline_remaining_fraction")
|
| 2678 |
+
if baseline is not None:
|
| 2679 |
+
remaining_pct = int(baseline * 100)
|
| 2680 |
+
group_stats["total_remaining_pcts"].append(
|
| 2681 |
+
remaining_pct
|
| 2682 |
+
)
|
| 2683 |
+
if baseline <= 0:
|
| 2684 |
+
group_stats["credentials_exhausted"] += 1
|
| 2685 |
+
|
| 2686 |
# Calculate average remaining percentage
|
| 2687 |
if group_stats["total_remaining_pcts"]:
|
| 2688 |
group_stats["avg_remaining_pct"] = int(
|
|
|
|
| 2699 |
models_data = cred.get("models", {})
|
| 2700 |
|
| 2701 |
for group_name, group_models in quota_groups.items():
|
| 2702 |
+
# Find representative model from this group (try all with alias fallback)
|
| 2703 |
+
model_stats = None
|
| 2704 |
for model in group_models:
|
| 2705 |
+
model_stats = self._find_model_stats_in_data(
|
| 2706 |
+
models_data, model, provider, provider_instance
|
| 2707 |
+
)
|
|
|
|
|
|
|
| 2708 |
if model_stats:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2709 |
break
|
| 2710 |
|
| 2711 |
+
if model_stats:
|
| 2712 |
+
baseline = model_stats.get("baseline_remaining_fraction")
|
| 2713 |
+
max_req = model_stats.get("quota_max_requests")
|
| 2714 |
+
req_count = model_stats.get("request_count", 0)
|
| 2715 |
+
reset_ts = model_stats.get("quota_reset_ts")
|
| 2716 |
+
|
| 2717 |
+
remaining_pct = (
|
| 2718 |
+
int(baseline * 100) if baseline is not None else None
|
| 2719 |
+
)
|
| 2720 |
+
is_exhausted = baseline is not None and baseline <= 0
|
| 2721 |
+
|
| 2722 |
+
# Format reset time
|
| 2723 |
+
reset_iso = None
|
| 2724 |
+
if reset_ts:
|
| 2725 |
+
try:
|
| 2726 |
+
from datetime import datetime, timezone
|
| 2727 |
+
|
| 2728 |
+
reset_iso = datetime.fromtimestamp(
|
| 2729 |
+
reset_ts, tz=timezone.utc
|
| 2730 |
+
).isoformat()
|
| 2731 |
+
except (ValueError, OSError):
|
| 2732 |
+
pass
|
| 2733 |
+
|
| 2734 |
+
cred["model_groups"][group_name] = {
|
| 2735 |
+
"remaining_pct": remaining_pct,
|
| 2736 |
+
"requests_used": req_count,
|
| 2737 |
+
"requests_max": max_req,
|
| 2738 |
+
"display": f"{req_count}/{max_req}"
|
| 2739 |
+
if max_req
|
| 2740 |
+
else f"{req_count}/?",
|
| 2741 |
+
"is_exhausted": is_exhausted,
|
| 2742 |
+
"reset_time_iso": reset_iso,
|
| 2743 |
+
"models": group_models,
|
| 2744 |
+
"confidence": self._get_baseline_confidence(
|
| 2745 |
+
model_stats
|
| 2746 |
+
),
|
| 2747 |
+
}
|
| 2748 |
+
|
| 2749 |
# Try to get email from provider's cache
|
| 2750 |
cred_path = cred.get("full_path", "")
|
| 2751 |
if hasattr(provider_instance, "project_tier_cache"):
|
|
|
|
| 2755 |
|
| 2756 |
return stats
|
| 2757 |
|
| 2758 |
+
def _find_model_stats_in_data(
|
| 2759 |
+
self,
|
| 2760 |
+
models_data: Dict[str, Any],
|
| 2761 |
+
model: str,
|
| 2762 |
+
provider: str,
|
| 2763 |
+
provider_instance: Any,
|
| 2764 |
+
) -> Optional[Dict[str, Any]]:
|
| 2765 |
+
"""
|
| 2766 |
+
Find model stats in models_data, trying various name variants.
|
| 2767 |
+
|
| 2768 |
+
Handles aliased model names (e.g., gemini-3-pro-preview -> gemini-3-pro-high)
|
| 2769 |
+
by using the provider's _user_to_api_model() mapping.
|
| 2770 |
+
|
| 2771 |
+
Args:
|
| 2772 |
+
models_data: Dict of model_name -> stats from credential
|
| 2773 |
+
model: Model name to look up (user-facing name)
|
| 2774 |
+
provider: Provider name for prefixing
|
| 2775 |
+
provider_instance: Provider instance for alias methods
|
| 2776 |
+
|
| 2777 |
+
Returns:
|
| 2778 |
+
Model stats dict if found, None otherwise
|
| 2779 |
+
"""
|
| 2780 |
+
# Try direct match with and without provider prefix
|
| 2781 |
+
prefixed_model = f"{provider}/{model}"
|
| 2782 |
+
model_stats = models_data.get(prefixed_model) or models_data.get(model)
|
| 2783 |
+
|
| 2784 |
+
if model_stats:
|
| 2785 |
+
return model_stats
|
| 2786 |
+
|
| 2787 |
+
# Try with API model name (e.g., gemini-3-pro-preview -> gemini-3-pro-high)
|
| 2788 |
+
if hasattr(provider_instance, "_user_to_api_model"):
|
| 2789 |
+
api_model = provider_instance._user_to_api_model(model)
|
| 2790 |
+
if api_model != model:
|
| 2791 |
+
prefixed_api = f"{provider}/{api_model}"
|
| 2792 |
+
model_stats = models_data.get(prefixed_api) or models_data.get(
|
| 2793 |
+
api_model
|
| 2794 |
+
)
|
| 2795 |
+
|
| 2796 |
+
return model_stats
|
| 2797 |
+
|
| 2798 |
def _get_baseline_confidence(self, model_stats: Dict) -> str:
|
| 2799 |
"""
|
| 2800 |
Determine confidence level based on baseline age.
|
src/rotator_library/usage_manager.py
CHANGED
|
@@ -392,6 +392,49 @@ class UsageManager:
|
|
| 392 |
# Not grouped - return individual model usage (no weight applied)
|
| 393 |
return self._get_usage_count(key, model, usage_field)
|
| 394 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
def _get_usage_field_name(self, credential: str) -> str:
|
| 396 |
"""
|
| 397 |
Get the usage tracking field name for a credential.
|
|
@@ -1285,9 +1328,10 @@ class UsageManager:
|
|
| 1285 |
if credential_tier_names
|
| 1286 |
else "unknown"
|
| 1287 |
)
|
|
|
|
| 1288 |
lib_logger.info(
|
| 1289 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1290 |
-
f"(tier: {tier_name}, priority: {priority_level}, selection: {selection_method},
|
| 1291 |
)
|
| 1292 |
return key
|
| 1293 |
|
|
@@ -1303,9 +1347,10 @@ class UsageManager:
|
|
| 1303 |
if credential_tier_names
|
| 1304 |
else "unknown"
|
| 1305 |
)
|
|
|
|
| 1306 |
lib_logger.info(
|
| 1307 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1308 |
-
f"(tier: {tier_name}, priority: {priority_level}, selection: {selection_method}, concurrent: {state['models_in_use'][model]}/{effective_max_concurrent},
|
| 1309 |
)
|
| 1310 |
return key
|
| 1311 |
|
|
@@ -1421,9 +1466,10 @@ class UsageManager:
|
|
| 1421 |
else None
|
| 1422 |
)
|
| 1423 |
tier_info = f"tier: {tier_name}, " if tier_name else ""
|
|
|
|
| 1424 |
lib_logger.info(
|
| 1425 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1426 |
-
f"({tier_info}selection: {selection_method},
|
| 1427 |
)
|
| 1428 |
return key
|
| 1429 |
|
|
@@ -1440,9 +1486,10 @@ class UsageManager:
|
|
| 1440 |
else None
|
| 1441 |
)
|
| 1442 |
tier_info = f"tier: {tier_name}, " if tier_name else ""
|
|
|
|
| 1443 |
lib_logger.info(
|
| 1444 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1445 |
-
f"({tier_info}selection: {selection_method}, concurrent: {state['models_in_use'][model]}/{effective_max_concurrent},
|
| 1446 |
)
|
| 1447 |
return key
|
| 1448 |
|
|
|
|
| 392 |
# Not grouped - return individual model usage (no weight applied)
|
| 393 |
return self._get_usage_count(key, model, usage_field)
|
| 394 |
|
| 395 |
+
def _get_quota_display(self, key: str, model: str) -> str:
|
| 396 |
+
"""
|
| 397 |
+
Get a formatted quota display string for logging.
|
| 398 |
+
|
| 399 |
+
For antigravity (providers in _REQUEST_COUNT_PROVIDERS), returns:
|
| 400 |
+
"quota: 170/250 [32%]" format
|
| 401 |
+
|
| 402 |
+
For other providers, returns:
|
| 403 |
+
"usage: 170" format (no max available)
|
| 404 |
+
|
| 405 |
+
Args:
|
| 406 |
+
key: Credential identifier
|
| 407 |
+
model: Model name (with provider prefix)
|
| 408 |
+
|
| 409 |
+
Returns:
|
| 410 |
+
Formatted string for logging
|
| 411 |
+
"""
|
| 412 |
+
provider = self._get_provider_from_credential(key)
|
| 413 |
+
|
| 414 |
+
if provider not in self._REQUEST_COUNT_PROVIDERS:
|
| 415 |
+
# Non-antigravity: just show usage count
|
| 416 |
+
usage = self._get_usage_count(key, model, "success_count")
|
| 417 |
+
return f"usage: {usage}"
|
| 418 |
+
|
| 419 |
+
# Antigravity: show quota display with remaining percentage
|
| 420 |
+
if self._usage_data is None:
|
| 421 |
+
return "quota: 0/? [100%]"
|
| 422 |
+
|
| 423 |
+
key_data = self._usage_data.get(key, {})
|
| 424 |
+
model_data = key_data.get("models", {}).get(model, {})
|
| 425 |
+
|
| 426 |
+
request_count = model_data.get("request_count", 0)
|
| 427 |
+
max_requests = model_data.get("quota_max_requests")
|
| 428 |
+
|
| 429 |
+
if max_requests:
|
| 430 |
+
remaining = max_requests - request_count
|
| 431 |
+
remaining_pct = (
|
| 432 |
+
int((remaining / max_requests) * 100) if max_requests > 0 else 0
|
| 433 |
+
)
|
| 434 |
+
return f"quota: {request_count}/{max_requests} [{remaining_pct}%]"
|
| 435 |
+
else:
|
| 436 |
+
return f"quota: {request_count}"
|
| 437 |
+
|
| 438 |
def _get_usage_field_name(self, credential: str) -> str:
|
| 439 |
"""
|
| 440 |
Get the usage tracking field name for a credential.
|
|
|
|
| 1328 |
if credential_tier_names
|
| 1329 |
else "unknown"
|
| 1330 |
)
|
| 1331 |
+
quota_display = self._get_quota_display(key, model)
|
| 1332 |
lib_logger.info(
|
| 1333 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1334 |
+
f"(tier: {tier_name}, priority: {priority_level}, selection: {selection_method}, {quota_display})"
|
| 1335 |
)
|
| 1336 |
return key
|
| 1337 |
|
|
|
|
| 1347 |
if credential_tier_names
|
| 1348 |
else "unknown"
|
| 1349 |
)
|
| 1350 |
+
quota_display = self._get_quota_display(key, model)
|
| 1351 |
lib_logger.info(
|
| 1352 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1353 |
+
f"(tier: {tier_name}, priority: {priority_level}, selection: {selection_method}, concurrent: {state['models_in_use'][model]}/{effective_max_concurrent}, {quota_display})"
|
| 1354 |
)
|
| 1355 |
return key
|
| 1356 |
|
|
|
|
| 1466 |
else None
|
| 1467 |
)
|
| 1468 |
tier_info = f"tier: {tier_name}, " if tier_name else ""
|
| 1469 |
+
quota_display = self._get_quota_display(key, model)
|
| 1470 |
lib_logger.info(
|
| 1471 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1472 |
+
f"({tier_info}selection: {selection_method}, {quota_display})"
|
| 1473 |
)
|
| 1474 |
return key
|
| 1475 |
|
|
|
|
| 1486 |
else None
|
| 1487 |
)
|
| 1488 |
tier_info = f"tier: {tier_name}, " if tier_name else ""
|
| 1489 |
+
quota_display = self._get_quota_display(key, model)
|
| 1490 |
lib_logger.info(
|
| 1491 |
f"Acquired key {mask_credential(key)} for model {model} "
|
| 1492 |
+
f"({tier_info}selection: {selection_method}, concurrent: {state['models_in_use'][model]}/{effective_max_concurrent}, {quota_display})"
|
| 1493 |
)
|
| 1494 |
return key
|
| 1495 |
|