pi-provider-antigravity 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +288 -256
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
6
6
|
|
|
7
7
|
// src/protocol.ts
|
|
8
8
|
var DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
9
|
-
var DEFAULT_USER_AGENT = "antigravity/cli/1.2.
|
|
9
|
+
var DEFAULT_USER_AGENT = "antigravity/cli/1.2.4 (aidev_client; os_type=linux; arch=amd64; cl=982146307; auth_method=consumer)";
|
|
10
10
|
var PROVIDER_ID = "antigravity";
|
|
11
11
|
var AntigravityHttpError = class extends Error {
|
|
12
12
|
status;
|
|
@@ -691,247 +691,26 @@ function createModelCatalog() {
|
|
|
691
691
|
};
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
-
// src/
|
|
694
|
+
// src/config.ts
|
|
695
695
|
import * as fs from "node:fs";
|
|
696
696
|
import * as os from "node:os";
|
|
697
697
|
import * as path from "node:path";
|
|
698
698
|
import { SettingsList } from "@earendil-works/pi-tui";
|
|
699
|
-
function renderProgressBar(fraction, width = 10) {
|
|
700
|
-
const clamped = Math.max(0, Math.min(1, fraction));
|
|
701
|
-
const filled = Math.round(clamped * width);
|
|
702
|
-
return `[${"#".repeat(filled)}${"-".repeat(width - filled)}]`;
|
|
703
|
-
}
|
|
704
|
-
var QUOTA_WINDOW_RANK = {
|
|
705
|
-
"5h": 0,
|
|
706
|
-
weekly: 1,
|
|
707
|
-
other: 2
|
|
708
|
-
};
|
|
709
|
-
function classifyQuotaWindow(bucket) {
|
|
710
|
-
const w = (bucket.window || "").trim().toLowerCase();
|
|
711
|
-
if (w === "5h") return "5h";
|
|
712
|
-
if (w === "weekly") return "weekly";
|
|
713
|
-
return "other";
|
|
714
|
-
}
|
|
715
|
-
function shortWindowLabel(bucket, kind) {
|
|
716
|
-
if (kind === "5h") return "5h";
|
|
717
|
-
if (kind === "weekly") return "Wk";
|
|
718
|
-
return bucket.displayName || bucket.bucketId || "Quota";
|
|
719
|
-
}
|
|
720
|
-
function sortQuotaBuckets(buckets) {
|
|
721
|
-
return buckets.slice().sort((a, b) => QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)]);
|
|
722
|
-
}
|
|
723
|
-
function formatResetRemaining(resetTime) {
|
|
724
|
-
if (!resetTime) return null;
|
|
725
|
-
const ts = Date.parse(resetTime);
|
|
726
|
-
if (!Number.isFinite(ts)) return null;
|
|
727
|
-
const delta = ts - Date.now();
|
|
728
|
-
if (delta <= 0) return "ready";
|
|
729
|
-
const totalMin = Math.ceil(delta / 6e4);
|
|
730
|
-
const days = Math.floor(totalMin / (60 * 24));
|
|
731
|
-
const hours = Math.floor(totalMin % (60 * 24) / 60);
|
|
732
|
-
const mins = totalMin % 60;
|
|
733
|
-
if (days > 0) return `in ${days}d ${hours}h`;
|
|
734
|
-
if (hours > 0) return `in ${hours}h ${mins}m`;
|
|
735
|
-
return `in ${mins}m`;
|
|
736
|
-
}
|
|
737
|
-
function parseQuotaSummary(data) {
|
|
738
|
-
const groups = [];
|
|
739
|
-
for (const g of data.groups || []) {
|
|
740
|
-
const buckets = [];
|
|
741
|
-
for (const b of g.buckets || []) {
|
|
742
|
-
buckets.push({
|
|
743
|
-
bucketId: b.bucketId || "unknown",
|
|
744
|
-
displayName: b.displayName || b.bucketId || "Limit",
|
|
745
|
-
window: b.window,
|
|
746
|
-
resetTime: b.resetTime,
|
|
747
|
-
description: b.description,
|
|
748
|
-
remainingFraction: typeof b.remainingFraction === "number" ? b.remainingFraction : 1
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
groups.push({
|
|
752
|
-
displayName: g.displayName || "Quota Group",
|
|
753
|
-
description: g.description,
|
|
754
|
-
buckets
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
return {
|
|
758
|
-
groups,
|
|
759
|
-
description: data.description
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
function formatQuotaSummary(summary) {
|
|
763
|
-
const lines = [];
|
|
764
|
-
for (const group of summary.groups) {
|
|
765
|
-
if (lines.length > 0) lines.push("");
|
|
766
|
-
lines.push(`${group.displayName}`);
|
|
767
|
-
for (const b of sortQuotaBuckets(group.buckets)) {
|
|
768
|
-
const pct = Math.round(b.remainingFraction * 100);
|
|
769
|
-
const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);
|
|
770
|
-
const reset = formatResetRemaining(b.resetTime);
|
|
771
|
-
const suffix = reset && pct < 100 ? ` (${reset})` : "";
|
|
772
|
-
lines.push(` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
return lines.join("\n");
|
|
776
|
-
}
|
|
777
|
-
function isGeminiQuotaGroup(group) {
|
|
778
|
-
return (group.buckets || []).some((b) => (b.bucketId || "").toLowerCase().includes("gemini"));
|
|
779
|
-
}
|
|
780
|
-
function selectQuotaGroup(groups, modelId) {
|
|
781
|
-
if (groups.length === 0) return void 0;
|
|
782
|
-
const family = classifyModelFamily(modelId);
|
|
783
|
-
if (family === "claude" || family === "gpt") {
|
|
784
|
-
const found = groups.find((g) => !isGeminiQuotaGroup(g));
|
|
785
|
-
if (found) return found;
|
|
786
|
-
}
|
|
787
|
-
return groups.find((g) => isGeminiQuotaGroup(g)) || groups[0];
|
|
788
|
-
}
|
|
789
|
-
var DEFAULT_WEEKLY_TO_5H_RATIO = 6;
|
|
790
|
-
function windowBucket(buckets, kind) {
|
|
791
|
-
return buckets.find((b) => classifyQuotaWindow(b) === kind);
|
|
792
|
-
}
|
|
793
|
-
function poolKey(bucket) {
|
|
794
|
-
const id = bucket.bucketId || "unknown";
|
|
795
|
-
return id.replace(/[-_](5h|five-?hour|weekly)$/i, "") || id;
|
|
796
|
-
}
|
|
797
|
-
function extractWindowFractionPairs(summary) {
|
|
798
|
-
const pairs = {};
|
|
799
|
-
for (const g of summary?.groups || []) {
|
|
800
|
-
const bucket5h = windowBucket(g.buckets, "5h");
|
|
801
|
-
const bucketWeekly = windowBucket(g.buckets, "weekly");
|
|
802
|
-
if (bucket5h && bucketWeekly) pairs[poolKey(bucket5h)] = { "5h": bucket5h.remainingFraction, weekly: bucketWeekly.remainingFraction };
|
|
803
|
-
}
|
|
804
|
-
return pairs;
|
|
805
|
-
}
|
|
806
|
-
function calibrateWeeklyTo5hRatio(previous, current, currentRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
|
|
807
|
-
if (!previous || !current) return currentRatio;
|
|
808
|
-
for (const key of Object.keys(current)) {
|
|
809
|
-
const prev = previous[key];
|
|
810
|
-
const curr = current[key];
|
|
811
|
-
if (!prev || !curr) continue;
|
|
812
|
-
const dFiveHour = prev["5h"] - curr["5h"];
|
|
813
|
-
const dWeekly = prev.weekly - curr.weekly;
|
|
814
|
-
if (dFiveHour > 1e-4 && dWeekly > 1e-5) {
|
|
815
|
-
const observedRatio = dFiveHour / dWeekly;
|
|
816
|
-
if (observedRatio >= 1 && observedRatio <= 20) {
|
|
817
|
-
return Math.round(observedRatio * 100) / 100;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
return currentRatio;
|
|
822
|
-
}
|
|
823
|
-
function selectUrgentBucket(group, weeklyTo5hRatio) {
|
|
824
|
-
const buckets = group.buckets;
|
|
825
|
-
if (buckets.length === 0) return void 0;
|
|
826
|
-
const bucket5h = windowBucket(buckets, "5h");
|
|
827
|
-
const bucketWeekly = windowBucket(buckets, "weekly");
|
|
828
|
-
if (bucket5h && bucketWeekly) {
|
|
829
|
-
return bucketWeekly.remainingFraction * weeklyTo5hRatio < bucket5h.remainingFraction ? bucketWeekly : bucket5h;
|
|
830
|
-
}
|
|
831
|
-
let worst = buckets[0];
|
|
832
|
-
for (const b of buckets) {
|
|
833
|
-
if (b.remainingFraction < worst.remainingFraction) worst = b;
|
|
834
|
-
}
|
|
835
|
-
return worst;
|
|
836
|
-
}
|
|
837
|
-
function buildQuotaFooter(summary, modelId, weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
|
|
838
|
-
const group = selectQuotaGroup(summary.groups, modelId);
|
|
839
|
-
if (!group) return void 0;
|
|
840
|
-
const urgent = selectUrgentBucket(group, weeklyTo5hRatio);
|
|
841
|
-
return urgent ? formatQuotaWindowPart(urgent) : void 0;
|
|
842
|
-
}
|
|
843
|
-
function buildQuotaFooterBoth(summary, modelId) {
|
|
844
|
-
const group = selectQuotaGroup(summary.groups, modelId);
|
|
845
|
-
if (!group || group.buckets.length === 0) return void 0;
|
|
846
|
-
return sortQuotaBuckets(group.buckets).map(formatQuotaWindowPart).join(" \xB7 ");
|
|
847
|
-
}
|
|
848
|
-
function formatQuotaWindowPart(bucket) {
|
|
849
|
-
const kind = classifyQuotaWindow(bucket);
|
|
850
|
-
const prefix = kind === "5h" ? "5h" : kind === "weekly" ? "Wk" : bucket.displayName || bucket.bucketId;
|
|
851
|
-
const pct = Math.round(bucket.remainingFraction * 100);
|
|
852
|
-
const short = formatResetRemaining(bucket.resetTime)?.replace(/^in /, "");
|
|
853
|
-
if (short && pct < 100) return `${prefix} ${pct}% (${short})`;
|
|
854
|
-
return `${prefix} ${pct}%`;
|
|
855
|
-
}
|
|
856
|
-
var ANSI_RED = "\x1B[31m";
|
|
857
|
-
var ANSI_YELLOW = "\x1B[33m";
|
|
858
699
|
var ANSI_FG_RESET = "\x1B[39m";
|
|
859
|
-
var
|
|
860
|
-
var QUOTA_ALERT_PCT = 10;
|
|
861
|
-
function colorizeQuotaFooter(footer) {
|
|
862
|
-
if (!footer) return void 0;
|
|
863
|
-
const match = footer.match(/(\d+)%/);
|
|
864
|
-
if (!match) return footer;
|
|
865
|
-
const pct = parseInt(match[1], 10);
|
|
866
|
-
if (pct <= QUOTA_ALERT_PCT) return `${ANSI_RED}${footer}${ANSI_FG_RESET}`;
|
|
867
|
-
if (pct <= QUOTA_WARN_PCT) return `${ANSI_YELLOW}${footer}${ANSI_FG_RESET}`;
|
|
868
|
-
return footer;
|
|
869
|
-
}
|
|
870
|
-
function colorizeQuotaFooterBoth(footer) {
|
|
871
|
-
if (!footer) return void 0;
|
|
872
|
-
return footer.split(" \xB7 ").map((part) => colorizeQuotaFooter(part) ?? part).join(" \xB7 ");
|
|
873
|
-
}
|
|
874
|
-
var QUOTA_STATUS_KEY = "pi-provider-antigravity-footer-usage";
|
|
875
|
-
var QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
876
|
-
var MAX_OBSERVATION_AGE_MS = 60 * 60 * 1e3;
|
|
877
|
-
function isFiniteNumber(value) {
|
|
878
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
879
|
-
}
|
|
880
|
-
function validPairs(raw) {
|
|
881
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
882
|
-
const pairs = {};
|
|
883
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
884
|
-
const rec = value;
|
|
885
|
-
const fiveHour = rec?.["5h"];
|
|
886
|
-
const weekly = rec?.weekly;
|
|
887
|
-
if (isFiniteNumber(fiveHour) && isFiniteNumber(weekly)) pairs[key] = { "5h": fiveHour, weekly };
|
|
888
|
-
}
|
|
889
|
-
return Object.keys(pairs).length > 0 ? pairs : void 0;
|
|
890
|
-
}
|
|
891
|
-
function isLiveCtx(ctx) {
|
|
892
|
-
try {
|
|
893
|
-
void ctx.ui;
|
|
894
|
-
return true;
|
|
895
|
-
} catch {
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
var FOOTER_MODES = Object.freeze({
|
|
900
|
-
off: {
|
|
901
|
-
key: "off",
|
|
902
|
-
render: () => ({})
|
|
903
|
-
},
|
|
904
|
-
smart: {
|
|
905
|
-
key: "smart",
|
|
906
|
-
note: "Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage",
|
|
907
|
-
render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO) => {
|
|
908
|
-
if (!summary) return {};
|
|
909
|
-
const plain = buildQuotaFooter(summary, modelId, ratio);
|
|
910
|
-
return { plain, colored: colorizeQuotaFooter(plain) };
|
|
911
|
-
}
|
|
912
|
-
},
|
|
913
|
-
all: {
|
|
914
|
-
key: "all",
|
|
915
|
-
note: "Lists every window of the pool backing the current model (5h or weekly)",
|
|
916
|
-
render: (summary, modelId) => {
|
|
917
|
-
if (!summary) return {};
|
|
918
|
-
const plain = buildQuotaFooterBoth(summary, modelId);
|
|
919
|
-
return { plain, colored: colorizeQuotaFooterBoth(plain) };
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
});
|
|
700
|
+
var PROVIDER_CONFIG_FILE = "pi-provider-antigravity.json";
|
|
923
701
|
var FOOTER_MODE_NOTES = Object.freeze({
|
|
924
|
-
smart:
|
|
925
|
-
all:
|
|
702
|
+
smart: "Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage",
|
|
703
|
+
all: "Lists every window of the pool backing the current model (5h or weekly)"
|
|
926
704
|
});
|
|
927
|
-
var FOOTER_MODE_OPTIONS = Object.freeze(
|
|
928
|
-
|
|
929
|
-
|
|
705
|
+
var FOOTER_MODE_OPTIONS = Object.freeze([
|
|
706
|
+
"off",
|
|
707
|
+
"smart",
|
|
708
|
+
"all"
|
|
709
|
+
]);
|
|
930
710
|
function normalizeFooterMode(value) {
|
|
931
711
|
const v = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
932
|
-
return v
|
|
712
|
+
return FOOTER_MODE_OPTIONS.includes(v) ? v : void 0;
|
|
933
713
|
}
|
|
934
|
-
var PROVIDER_CONFIG_FILE = "pi-provider-antigravity.json";
|
|
935
714
|
function defaultConfigFile(env = process.env) {
|
|
936
715
|
const dir = (env.PI_CODING_AGENT_DIR || "").trim() || path.join(os.homedir(), ".pi", "agent");
|
|
937
716
|
return path.join(dir, PROVIDER_CONFIG_FILE);
|
|
@@ -985,21 +764,23 @@ function updateProviderConfig(file, mutate) {
|
|
|
985
764
|
function resolveFooterMode(config) {
|
|
986
765
|
return normalizeFooterMode(config?.settings?.quotaFooter) ?? "off";
|
|
987
766
|
}
|
|
767
|
+
function loadSubsystemState(subsystem, file = defaultConfigFile()) {
|
|
768
|
+
const state = loadProviderConfig(file)?.states?.[subsystem];
|
|
769
|
+
return state && typeof state === "object" && !Array.isArray(state) ? state : void 0;
|
|
770
|
+
}
|
|
771
|
+
function saveSubsystemState(subsystem, state, file = defaultConfigFile()) {
|
|
772
|
+
return updateProviderConfig(file, (config) => {
|
|
773
|
+
const states = config.states && typeof config.states === "object" && !Array.isArray(config.states) ? config.states : {};
|
|
774
|
+
const current = states[subsystem] && typeof states[subsystem] === "object" && !Array.isArray(states[subsystem]) ? states[subsystem] : {};
|
|
775
|
+
states[subsystem] = { ...current, ...state };
|
|
776
|
+
config.states = states;
|
|
777
|
+
});
|
|
778
|
+
}
|
|
988
779
|
function fileQuotaStatusStore(file = defaultConfigFile()) {
|
|
989
780
|
return {
|
|
990
781
|
loadMode: () => resolveFooterMode(loadProviderConfig(file)),
|
|
991
|
-
loadQuotaState: () =>
|
|
992
|
-
|
|
993
|
-
return quota && typeof quota === "object" && !Array.isArray(quota) ? quota : void 0;
|
|
994
|
-
},
|
|
995
|
-
// Merges state into the existing file, preserving settings and unknown
|
|
996
|
-
// keys. Never clobbers a file we couldn't parse.
|
|
997
|
-
saveQuotaState: (state) => updateProviderConfig(file, (config) => {
|
|
998
|
-
const states = config.states && typeof config.states === "object" && !Array.isArray(config.states) ? config.states : {};
|
|
999
|
-
const quota = states.quota && typeof states.quota === "object" && !Array.isArray(states.quota) ? states.quota : {};
|
|
1000
|
-
states.quota = { ...quota, ...state };
|
|
1001
|
-
config.states = states;
|
|
1002
|
-
})
|
|
782
|
+
loadQuotaState: () => loadSubsystemState("quota", file),
|
|
783
|
+
saveQuotaState: (state) => saveSubsystemState("quota", state, file)
|
|
1003
784
|
};
|
|
1004
785
|
}
|
|
1005
786
|
var SETTINGS_FIELDS = Object.freeze([
|
|
@@ -1039,11 +820,11 @@ async function applySettingValue(field, value, file, ctx, quotaStatus) {
|
|
|
1039
820
|
function writeFailedMessage(file) {
|
|
1040
821
|
return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;
|
|
1041
822
|
}
|
|
1042
|
-
function previewQuotaFooterText(coord, modelId, mode) {
|
|
823
|
+
function previewQuotaFooterText(coord, modelId, mode, style) {
|
|
1043
824
|
if (!coord) return void 0;
|
|
1044
825
|
const normalized = normalizeFooterMode(mode);
|
|
1045
826
|
if (!normalized || normalized === "off") return void 0;
|
|
1046
|
-
const { colored } = coord.renderFooter(modelId, normalized);
|
|
827
|
+
const { colored } = coord.renderFooter(modelId, normalized, style);
|
|
1047
828
|
if (!colored) return void 0;
|
|
1048
829
|
return ANSI_FG_RESET + colored;
|
|
1049
830
|
}
|
|
@@ -1117,7 +898,7 @@ async function openSettingsDialog(ctx, quotaStatus, file) {
|
|
|
1117
898
|
const item = items.find((i) => i.id === "quotaFooter");
|
|
1118
899
|
if (!item) return;
|
|
1119
900
|
const base = quotaField?.description ?? "";
|
|
1120
|
-
const sample = mode === "off" ? "hidden" : previewQuotaFooterText(quotaStatus, ctx.model?.id, mode);
|
|
901
|
+
const sample = mode === "off" ? "hidden" : previewQuotaFooterText(quotaStatus, ctx.model?.id, mode, theme);
|
|
1121
902
|
const head = sample && base ? `${base}: ${sample}` : sample ?? base;
|
|
1122
903
|
const note = quotaField?.optionNotes?.[mode];
|
|
1123
904
|
item.description = note ? `${head}
|
|
@@ -1152,12 +933,262 @@ ${note}` : head;
|
|
|
1152
933
|
render: (width) => [theme.fg("border", "\u2500".repeat(Math.max(1, width)))]
|
|
1153
934
|
};
|
|
1154
935
|
return {
|
|
1155
|
-
render: (width) => [
|
|
936
|
+
render: (width) => [
|
|
937
|
+
...border.render(width),
|
|
938
|
+
...list.render(width),
|
|
939
|
+
...border.render(width)
|
|
940
|
+
],
|
|
1156
941
|
handleInput: (data) => list.handleInput(data),
|
|
1157
942
|
invalidate: () => list.invalidate()
|
|
1158
943
|
};
|
|
1159
944
|
});
|
|
1160
945
|
}
|
|
946
|
+
|
|
947
|
+
// src/quota-status.ts
|
|
948
|
+
function renderProgressBar(fraction, width = 10) {
|
|
949
|
+
const clamped = Math.max(0, Math.min(1, fraction));
|
|
950
|
+
const filled = Math.round(clamped * width);
|
|
951
|
+
return `[${"#".repeat(filled)}${"-".repeat(width - filled)}]`;
|
|
952
|
+
}
|
|
953
|
+
var QUOTA_WINDOW_RANK = {
|
|
954
|
+
"5h": 0,
|
|
955
|
+
weekly: 1,
|
|
956
|
+
other: 2
|
|
957
|
+
};
|
|
958
|
+
function classifyQuotaWindow(bucket) {
|
|
959
|
+
const w = (bucket.window || "").trim().toLowerCase();
|
|
960
|
+
if (w === "5h") return "5h";
|
|
961
|
+
if (w === "weekly") return "weekly";
|
|
962
|
+
return "other";
|
|
963
|
+
}
|
|
964
|
+
function shortWindowLabel(bucket, kind) {
|
|
965
|
+
if (kind === "5h") return "5h";
|
|
966
|
+
if (kind === "weekly") return "Wk";
|
|
967
|
+
return bucket.displayName || bucket.bucketId || "Quota";
|
|
968
|
+
}
|
|
969
|
+
function sortQuotaBuckets(buckets) {
|
|
970
|
+
return buckets.slice().sort(
|
|
971
|
+
(a, b) => QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)]
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
function formatResetRemaining(resetTime) {
|
|
975
|
+
if (!resetTime) return null;
|
|
976
|
+
const ts = Date.parse(resetTime);
|
|
977
|
+
if (!Number.isFinite(ts)) return null;
|
|
978
|
+
const delta = ts - Date.now();
|
|
979
|
+
if (delta <= 0) return "ready";
|
|
980
|
+
const totalMin = Math.ceil(delta / 6e4);
|
|
981
|
+
const days = Math.floor(totalMin / (60 * 24));
|
|
982
|
+
const hours = Math.floor(totalMin % (60 * 24) / 60);
|
|
983
|
+
const mins = totalMin % 60;
|
|
984
|
+
if (days > 0) return `in ${days}d ${hours}h`;
|
|
985
|
+
if (hours > 0) return `in ${hours}h ${mins}m`;
|
|
986
|
+
return `in ${mins}m`;
|
|
987
|
+
}
|
|
988
|
+
function parseQuotaSummary(data) {
|
|
989
|
+
const groups = [];
|
|
990
|
+
for (const g of data.groups || []) {
|
|
991
|
+
const buckets = [];
|
|
992
|
+
for (const b of g.buckets || []) {
|
|
993
|
+
buckets.push({
|
|
994
|
+
bucketId: b.bucketId || "unknown",
|
|
995
|
+
displayName: b.displayName || b.bucketId || "Limit",
|
|
996
|
+
window: b.window,
|
|
997
|
+
resetTime: b.resetTime,
|
|
998
|
+
description: b.description,
|
|
999
|
+
remainingFraction: typeof b.remainingFraction === "number" ? b.remainingFraction : 1
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
groups.push({
|
|
1003
|
+
displayName: g.displayName || "Quota Group",
|
|
1004
|
+
description: g.description,
|
|
1005
|
+
buckets
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
return {
|
|
1009
|
+
groups,
|
|
1010
|
+
description: data.description
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
function formatQuotaSummary(summary) {
|
|
1014
|
+
const lines = [];
|
|
1015
|
+
for (const group of summary.groups) {
|
|
1016
|
+
if (lines.length > 0) lines.push("");
|
|
1017
|
+
lines.push(`${group.displayName}`);
|
|
1018
|
+
for (const b of sortQuotaBuckets(group.buckets)) {
|
|
1019
|
+
const pct = Math.round(b.remainingFraction * 100);
|
|
1020
|
+
const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);
|
|
1021
|
+
const reset = formatResetRemaining(b.resetTime);
|
|
1022
|
+
const suffix = reset && pct < 100 ? ` (${reset})` : "";
|
|
1023
|
+
lines.push(
|
|
1024
|
+
` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return lines.join("\n");
|
|
1029
|
+
}
|
|
1030
|
+
function isGeminiQuotaGroup(group) {
|
|
1031
|
+
return (group.buckets || []).some((b) => (b.bucketId || "").toLowerCase().includes("gemini"));
|
|
1032
|
+
}
|
|
1033
|
+
function selectQuotaGroup(groups, modelId) {
|
|
1034
|
+
if (groups.length === 0) return void 0;
|
|
1035
|
+
const family = classifyModelFamily(modelId);
|
|
1036
|
+
if (family === "claude" || family === "gpt") {
|
|
1037
|
+
const found = groups.find((g) => !isGeminiQuotaGroup(g));
|
|
1038
|
+
if (found) return found;
|
|
1039
|
+
}
|
|
1040
|
+
return groups.find((g) => isGeminiQuotaGroup(g)) || groups[0];
|
|
1041
|
+
}
|
|
1042
|
+
var DEFAULT_WEEKLY_TO_5H_RATIO = 6;
|
|
1043
|
+
function windowBucket(buckets, kind) {
|
|
1044
|
+
return buckets.find((b) => classifyQuotaWindow(b) === kind);
|
|
1045
|
+
}
|
|
1046
|
+
function poolKey(bucket) {
|
|
1047
|
+
const id = bucket.bucketId || "unknown";
|
|
1048
|
+
return id.replace(/[-_](5h|five-?hour|weekly)$/i, "") || id;
|
|
1049
|
+
}
|
|
1050
|
+
function extractWindowFractionPairs(summary) {
|
|
1051
|
+
const pairs = {};
|
|
1052
|
+
for (const g of summary?.groups || []) {
|
|
1053
|
+
const bucket5h = windowBucket(g.buckets, "5h");
|
|
1054
|
+
const bucketWeekly = windowBucket(g.buckets, "weekly");
|
|
1055
|
+
if (bucket5h && bucketWeekly)
|
|
1056
|
+
pairs[poolKey(bucket5h)] = {
|
|
1057
|
+
"5h": bucket5h.remainingFraction,
|
|
1058
|
+
weekly: bucketWeekly.remainingFraction
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
return pairs;
|
|
1062
|
+
}
|
|
1063
|
+
function calibrateWeeklyTo5hRatio(previous, current, currentRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
|
|
1064
|
+
if (!previous || !current) return currentRatio;
|
|
1065
|
+
for (const key of Object.keys(current)) {
|
|
1066
|
+
const prev = previous[key];
|
|
1067
|
+
const curr = current[key];
|
|
1068
|
+
if (!prev || !curr) continue;
|
|
1069
|
+
const dFiveHour = prev["5h"] - curr["5h"];
|
|
1070
|
+
const dWeekly = prev.weekly - curr.weekly;
|
|
1071
|
+
if (dFiveHour > 1e-4 && dWeekly > 1e-5) {
|
|
1072
|
+
const observedRatio = dFiveHour / dWeekly;
|
|
1073
|
+
if (observedRatio >= 1 && observedRatio <= 20) {
|
|
1074
|
+
return Math.round(observedRatio * 100) / 100;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return currentRatio;
|
|
1079
|
+
}
|
|
1080
|
+
function selectUrgentBucket(group, weeklyTo5hRatio) {
|
|
1081
|
+
const buckets = group.buckets;
|
|
1082
|
+
if (buckets.length === 0) return void 0;
|
|
1083
|
+
const bucket5h = windowBucket(buckets, "5h");
|
|
1084
|
+
const bucketWeekly = windowBucket(buckets, "weekly");
|
|
1085
|
+
if (bucket5h && bucketWeekly) {
|
|
1086
|
+
return bucketWeekly.remainingFraction * weeklyTo5hRatio < bucket5h.remainingFraction ? bucketWeekly : bucket5h;
|
|
1087
|
+
}
|
|
1088
|
+
let worst = buckets[0];
|
|
1089
|
+
for (const b of buckets) {
|
|
1090
|
+
if (b.remainingFraction < worst.remainingFraction) worst = b;
|
|
1091
|
+
}
|
|
1092
|
+
return worst;
|
|
1093
|
+
}
|
|
1094
|
+
function buildQuotaFooter(summary, modelId, weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
|
|
1095
|
+
const group = selectQuotaGroup(summary.groups, modelId);
|
|
1096
|
+
if (!group) return void 0;
|
|
1097
|
+
const urgent = selectUrgentBucket(group, weeklyTo5hRatio);
|
|
1098
|
+
return urgent ? formatQuotaWindowPart(urgent) : void 0;
|
|
1099
|
+
}
|
|
1100
|
+
function buildQuotaFooterBoth(summary, modelId) {
|
|
1101
|
+
const group = selectQuotaGroup(summary.groups, modelId);
|
|
1102
|
+
if (!group || group.buckets.length === 0) return void 0;
|
|
1103
|
+
return sortQuotaBuckets(group.buckets).map(formatQuotaWindowPart).join(" \xB7 ");
|
|
1104
|
+
}
|
|
1105
|
+
function formatQuotaWindowPart(bucket) {
|
|
1106
|
+
const kind = classifyQuotaWindow(bucket);
|
|
1107
|
+
const prefix = kind === "5h" ? "5h" : kind === "weekly" ? "Wk" : bucket.displayName || bucket.bucketId;
|
|
1108
|
+
const pct = Math.round(bucket.remainingFraction * 100);
|
|
1109
|
+
const short = formatResetRemaining(bucket.resetTime)?.replace(/^in /, "");
|
|
1110
|
+
if (short && pct < 100) return `${prefix} ${pct}% (${short})`;
|
|
1111
|
+
return `${prefix} ${pct}%`;
|
|
1112
|
+
}
|
|
1113
|
+
var ANSI_RED = "\x1B[31m";
|
|
1114
|
+
var ANSI_YELLOW = "\x1B[33m";
|
|
1115
|
+
var ANSI_DIM = "\x1B[90m";
|
|
1116
|
+
var ANSI_FG_RESET2 = "\x1B[39m";
|
|
1117
|
+
var QUOTA_WARN_PCT = 30;
|
|
1118
|
+
var QUOTA_ALERT_PCT = 10;
|
|
1119
|
+
function resolveColorizer(style) {
|
|
1120
|
+
if (typeof style === "function") return style;
|
|
1121
|
+
if (style && typeof style.fg === "function") return (tone, text) => style.fg(tone, text);
|
|
1122
|
+
return (tone, text) => {
|
|
1123
|
+
const code = tone === "error" ? ANSI_RED : tone === "warning" ? ANSI_YELLOW : ANSI_DIM;
|
|
1124
|
+
return `${code}${text}${ANSI_FG_RESET2}`;
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function colorizeQuotaFooter(footer, style) {
|
|
1128
|
+
if (!footer) return void 0;
|
|
1129
|
+
const color = resolveColorizer(style);
|
|
1130
|
+
const match = footer.match(/(\d+)%/);
|
|
1131
|
+
if (!match) return color("dim", footer);
|
|
1132
|
+
const pct = parseInt(match[1], 10);
|
|
1133
|
+
if (pct <= QUOTA_ALERT_PCT) return color("error", footer);
|
|
1134
|
+
if (pct <= QUOTA_WARN_PCT) return color("warning", footer);
|
|
1135
|
+
return color("dim", footer);
|
|
1136
|
+
}
|
|
1137
|
+
function colorizeQuotaFooterBoth(footer, style) {
|
|
1138
|
+
if (!footer) return void 0;
|
|
1139
|
+
const color = resolveColorizer(style);
|
|
1140
|
+
const sep = color("dim", " \xB7 ");
|
|
1141
|
+
return footer.split(" \xB7 ").map((part) => colorizeQuotaFooter(part, style) ?? part).join(sep);
|
|
1142
|
+
}
|
|
1143
|
+
var QUOTA_STATUS_KEY = "pi-provider-antigravity-footer-usage";
|
|
1144
|
+
var QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1145
|
+
var MAX_OBSERVATION_AGE_MS = 60 * 60 * 1e3;
|
|
1146
|
+
function isFiniteNumber(value) {
|
|
1147
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1148
|
+
}
|
|
1149
|
+
function validPairs(raw) {
|
|
1150
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
1151
|
+
const pairs = {};
|
|
1152
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1153
|
+
const rec = value;
|
|
1154
|
+
const fiveHour = rec?.["5h"];
|
|
1155
|
+
const weekly = rec?.weekly;
|
|
1156
|
+
if (isFiniteNumber(fiveHour) && isFiniteNumber(weekly)) pairs[key] = { "5h": fiveHour, weekly };
|
|
1157
|
+
}
|
|
1158
|
+
return Object.keys(pairs).length > 0 ? pairs : void 0;
|
|
1159
|
+
}
|
|
1160
|
+
function isLiveCtx(ctx) {
|
|
1161
|
+
try {
|
|
1162
|
+
void ctx.ui;
|
|
1163
|
+
return true;
|
|
1164
|
+
} catch {
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
var FOOTER_MODES = Object.freeze({
|
|
1169
|
+
off: {
|
|
1170
|
+
key: "off",
|
|
1171
|
+
render: () => ({})
|
|
1172
|
+
},
|
|
1173
|
+
smart: {
|
|
1174
|
+
key: "smart",
|
|
1175
|
+
note: FOOTER_MODE_NOTES.smart,
|
|
1176
|
+
render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO, style) => {
|
|
1177
|
+
if (!summary) return {};
|
|
1178
|
+
const plain = buildQuotaFooter(summary, modelId, ratio);
|
|
1179
|
+
return { plain, colored: colorizeQuotaFooter(plain, style) };
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
all: {
|
|
1183
|
+
key: "all",
|
|
1184
|
+
note: FOOTER_MODE_NOTES.all,
|
|
1185
|
+
render: (summary, modelId, _ratio, style) => {
|
|
1186
|
+
if (!summary) return {};
|
|
1187
|
+
const plain = buildQuotaFooterBoth(summary, modelId);
|
|
1188
|
+
return { plain, colored: colorizeQuotaFooterBoth(plain, style) };
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1161
1192
|
function isAntigravityModel(model) {
|
|
1162
1193
|
return model?.provider === PROVIDER_ID;
|
|
1163
1194
|
}
|
|
@@ -1187,8 +1218,8 @@ var QuotaStatusCoordinator = class {
|
|
|
1187
1218
|
footerFor(modelId, mode = "smart") {
|
|
1188
1219
|
return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio).plain;
|
|
1189
1220
|
}
|
|
1190
|
-
renderFooter(modelId, mode = "smart") {
|
|
1191
|
-
return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio) ?? {};
|
|
1221
|
+
renderFooter(modelId, mode = "smart", style) {
|
|
1222
|
+
return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, style) ?? {};
|
|
1192
1223
|
}
|
|
1193
1224
|
get isFresh() {
|
|
1194
1225
|
return !!this.summary && Date.now() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;
|
|
@@ -1218,7 +1249,8 @@ var QuotaStatusCoordinator = class {
|
|
|
1218
1249
|
ctx.ui.setStatus(QUOTA_STATUS_KEY, void 0);
|
|
1219
1250
|
return;
|
|
1220
1251
|
}
|
|
1221
|
-
const
|
|
1252
|
+
const theme = ctx.ui?.theme;
|
|
1253
|
+
const { colored } = this.renderFooter(ctx.model?.id, mode, theme);
|
|
1222
1254
|
ctx.ui.setStatus(QUOTA_STATUS_KEY, colored);
|
|
1223
1255
|
}
|
|
1224
1256
|
// Atomic inspect for /antigravity usage: forces fresh quota fetch, repaints
|
|
@@ -1231,10 +1263,6 @@ var QuotaStatusCoordinator = class {
|
|
|
1231
1263
|
this.paint(ctx);
|
|
1232
1264
|
return formatQuotaSummary(summary);
|
|
1233
1265
|
}
|
|
1234
|
-
// Opens provider settings dialog/menu and updates state atomically.
|
|
1235
|
-
async openSettings(ctx) {
|
|
1236
|
-
await openSettings(ctx, this);
|
|
1237
|
-
}
|
|
1238
1266
|
// One fetch for preview purposes even when the footer slot is off or the
|
|
1239
1267
|
// current model belongs to another provider: opening quota settings is an
|
|
1240
1268
|
// explicit look at quota (like /antigravity usage), and the preview is the
|
|
@@ -1270,7 +1298,11 @@ var QuotaStatusCoordinator = class {
|
|
|
1270
1298
|
// Accepts an externally fetched summary into the shared cache: same
|
|
1271
1299
|
// calibration, persistence, and freshness as fetch().
|
|
1272
1300
|
ingest(summary) {
|
|
1273
|
-
const calibrated = calibrateWeeklyTo5hRatio(
|
|
1301
|
+
const calibrated = calibrateWeeklyTo5hRatio(
|
|
1302
|
+
this.lastPairs,
|
|
1303
|
+
extractWindowFractionPairs(summary),
|
|
1304
|
+
this.weeklyTo5hRatio
|
|
1305
|
+
);
|
|
1274
1306
|
this.weeklyTo5hRatio = calibrated;
|
|
1275
1307
|
this.lastPairs = extractWindowFractionPairs(summary);
|
|
1276
1308
|
this.summary = summary;
|