pi-provider-antigravity 0.11.0 → 0.13.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 +369 -283
- package/dist/index.js.map +3 -3
- package/package.json +7 -8
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.5 (aidev_client; os_type=linux; arch=amd64; cl=982839923; auth_method=consumer)";
|
|
10
10
|
var PROVIDER_ID = "antigravity";
|
|
11
11
|
var AntigravityHttpError = class extends Error {
|
|
12
12
|
status;
|
|
@@ -254,7 +254,24 @@ function parseStoredCredentials(rawApiKey) {
|
|
|
254
254
|
return { token: rawApiKey, projectId: FALLBACK_PROJECT_ID };
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
// src/
|
|
257
|
+
// src/model-identity.ts
|
|
258
|
+
function classifyModelFamily(modelId) {
|
|
259
|
+
const bare = ((modelId || "").split("/").pop() || "").toLowerCase();
|
|
260
|
+
if (bare.startsWith("gemini-")) return "gemini";
|
|
261
|
+
if (bare.startsWith("claude-")) return "claude";
|
|
262
|
+
if (bare.startsWith("gpt-")) return "gpt";
|
|
263
|
+
return "unknown";
|
|
264
|
+
}
|
|
265
|
+
function isCompatibleFamily(msgModel, targetModelId) {
|
|
266
|
+
if (!msgModel || !targetModelId) return true;
|
|
267
|
+
if (msgModel === targetModelId) return true;
|
|
268
|
+
const msgFamily = classifyModelFamily(msgModel);
|
|
269
|
+
if (msgFamily !== "unknown" && msgFamily === classifyModelFamily(targetModelId)) return true;
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/model-catalog.ts
|
|
274
|
+
var PRIVATE_SNAPSHOT_KEY = "pi-provider-antigravity";
|
|
258
275
|
var CANONICAL_TIER_SUFFIXES = Object.freeze({
|
|
259
276
|
low: "-low",
|
|
260
277
|
medium: "-medium",
|
|
@@ -305,23 +322,6 @@ function tierCandidateOrder(effort) {
|
|
|
305
322
|
const fallbacks = TIER_FALLBACKS[effort] ?? DEFAULT_TIER_ORDER;
|
|
306
323
|
return [.../* @__PURE__ */ new Set([...canonical, ...fallbacks])];
|
|
307
324
|
}
|
|
308
|
-
function classifyModelFamily(modelId) {
|
|
309
|
-
const bare = ((modelId || "").split("/").pop() || "").toLowerCase();
|
|
310
|
-
if (bare.startsWith("gemini-")) return "gemini";
|
|
311
|
-
if (bare.startsWith("claude-")) return "claude";
|
|
312
|
-
if (bare.startsWith("gpt-")) return "gpt";
|
|
313
|
-
return "unknown";
|
|
314
|
-
}
|
|
315
|
-
function isCompatibleFamily(msgModel, targetModelId) {
|
|
316
|
-
if (!msgModel || !targetModelId) return true;
|
|
317
|
-
if (msgModel === targetModelId) return true;
|
|
318
|
-
const msgFamily = classifyModelFamily(msgModel);
|
|
319
|
-
if (msgFamily !== "unknown" && msgFamily === classifyModelFamily(targetModelId)) return true;
|
|
320
|
-
return extractBaseModelId(msgModel) === extractBaseModelId(targetModelId);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// src/model-catalog.ts
|
|
324
|
-
var PRIVATE_SNAPSHOT_KEY = "pi-provider-antigravity";
|
|
325
325
|
function buildThinkingMap(models) {
|
|
326
326
|
return Object.fromEntries(
|
|
327
327
|
models.map((m) => [
|
|
@@ -691,11 +691,268 @@ 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
|
+
var PROVIDER_CONFIG_FILE = "pi-provider-antigravity.json";
|
|
700
|
+
function defaultConfigFile(env = process.env) {
|
|
701
|
+
const dir = (env.PI_CODING_AGENT_DIR || "").trim() || path.join(os.homedir(), ".pi", "agent");
|
|
702
|
+
return path.join(dir, PROVIDER_CONFIG_FILE);
|
|
703
|
+
}
|
|
704
|
+
function loadProviderConfig(file = defaultConfigFile()) {
|
|
705
|
+
try {
|
|
706
|
+
const raw = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
707
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
710
|
+
return void 0;
|
|
711
|
+
}
|
|
712
|
+
function writeProviderConfigAtomically(file, data) {
|
|
713
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
714
|
+
try {
|
|
715
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
716
|
+
const mode = fs.existsSync(file) ? fs.statSync(file).mode & 511 : void 0;
|
|
717
|
+
const fd = fs.openSync(tmp, "w", mode);
|
|
718
|
+
try {
|
|
719
|
+
fs.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
720
|
+
fs.fsyncSync(fd);
|
|
721
|
+
} finally {
|
|
722
|
+
fs.closeSync(fd);
|
|
723
|
+
}
|
|
724
|
+
fs.renameSync(tmp, file);
|
|
725
|
+
return true;
|
|
726
|
+
} catch {
|
|
727
|
+
try {
|
|
728
|
+
fs.rmSync(tmp, { force: true });
|
|
729
|
+
} catch {
|
|
730
|
+
}
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function updateProviderConfig(file, mutate) {
|
|
735
|
+
try {
|
|
736
|
+
let data = {};
|
|
737
|
+
try {
|
|
738
|
+
const raw = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
739
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
|
|
740
|
+
data = raw;
|
|
741
|
+
} catch (err) {
|
|
742
|
+
if (err?.code !== "ENOENT") return false;
|
|
743
|
+
}
|
|
744
|
+
mutate(data);
|
|
745
|
+
return writeProviderConfigAtomically(file, data);
|
|
746
|
+
} catch {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function loadSubsystemState(subsystem, file = defaultConfigFile()) {
|
|
751
|
+
const state = loadProviderConfig(file)?.states?.[subsystem];
|
|
752
|
+
return state && typeof state === "object" && !Array.isArray(state) ? state : void 0;
|
|
753
|
+
}
|
|
754
|
+
function saveSubsystemState(subsystem, state, file = defaultConfigFile()) {
|
|
755
|
+
return updateProviderConfig(file, (config) => {
|
|
756
|
+
const states = config.states && typeof config.states === "object" && !Array.isArray(config.states) ? config.states : {};
|
|
757
|
+
const current = states[subsystem] && typeof states[subsystem] === "object" && !Array.isArray(states[subsystem]) ? states[subsystem] : {};
|
|
758
|
+
states[subsystem] = { ...current, ...state };
|
|
759
|
+
config.states = states;
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
var SettingsRegistry = class {
|
|
763
|
+
fields = [];
|
|
764
|
+
register(field) {
|
|
765
|
+
const existingIndex = this.fields.findIndex((f) => f.key === field.key);
|
|
766
|
+
if (existingIndex >= 0) {
|
|
767
|
+
this.fields[existingIndex] = field;
|
|
768
|
+
} else {
|
|
769
|
+
this.fields.push(field);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
unregister(key) {
|
|
773
|
+
this.fields = this.fields.filter((f) => f.key !== key);
|
|
774
|
+
}
|
|
775
|
+
getFields() {
|
|
776
|
+
return [...this.fields];
|
|
777
|
+
}
|
|
778
|
+
clear() {
|
|
779
|
+
this.fields = [];
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
var defaultSettingsRegistry = new SettingsRegistry();
|
|
783
|
+
function registerSettingField(field) {
|
|
784
|
+
defaultSettingsRegistry.register(field);
|
|
785
|
+
}
|
|
786
|
+
function getRegisteredSettingFields() {
|
|
787
|
+
return defaultSettingsRegistry.getFields();
|
|
788
|
+
}
|
|
789
|
+
function fieldDisplayValue(config, field) {
|
|
790
|
+
const raw = config?.settings?.[field.key];
|
|
791
|
+
if (typeof raw === "string" && raw.trim()) return raw.trim();
|
|
792
|
+
return field.defaultValue ?? "unset";
|
|
793
|
+
}
|
|
794
|
+
function saveSettingValue(field, value, file) {
|
|
795
|
+
return updateProviderConfig(file, (config) => {
|
|
796
|
+
const settings = config.settings && typeof config.settings === "object" && !Array.isArray(config.settings) ? config.settings : {};
|
|
797
|
+
settings[field.key] = value;
|
|
798
|
+
config.settings = settings;
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
async function applySettingValue(field, value, file, ctx) {
|
|
802
|
+
if (!saveSettingValue(field, value, file)) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
await field.onChange?.(ctx, value);
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
function writeFailedMessage(file) {
|
|
809
|
+
return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;
|
|
810
|
+
}
|
|
811
|
+
function buildSettingsItems(config, fields = getRegisteredSettingFields()) {
|
|
812
|
+
return fields.map((f) => ({
|
|
813
|
+
id: f.key,
|
|
814
|
+
label: f.label,
|
|
815
|
+
description: f.description,
|
|
816
|
+
currentValue: fieldDisplayValue(config, f),
|
|
817
|
+
values: [...f.options]
|
|
818
|
+
}));
|
|
819
|
+
}
|
|
820
|
+
async function settingsListTheme(theme) {
|
|
821
|
+
try {
|
|
822
|
+
const pi = await import("@earendil-works/pi-coding-agent");
|
|
823
|
+
return pi.getSettingsListTheme();
|
|
824
|
+
} catch {
|
|
825
|
+
return {
|
|
826
|
+
label: (text, selected) => selected ? theme.fg("accent", text) : text,
|
|
827
|
+
value: (text, selected) => selected ? theme.fg("accent", text) : theme.fg("muted", text),
|
|
828
|
+
description: (text) => theme.fg("dim", text),
|
|
829
|
+
cursor: theme.fg("accent", "\u2192 "),
|
|
830
|
+
hint: (text) => theme.fg("dim", text)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function resolveFields(registryOrFields) {
|
|
835
|
+
if (!registryOrFields) return getRegisteredSettingFields();
|
|
836
|
+
if ("getFields" in registryOrFields && typeof registryOrFields.getFields === "function") {
|
|
837
|
+
return registryOrFields.getFields();
|
|
838
|
+
}
|
|
839
|
+
return registryOrFields;
|
|
840
|
+
}
|
|
841
|
+
async function openSettings(ctx, registryOrFields) {
|
|
842
|
+
const fields = resolveFields(registryOrFields);
|
|
843
|
+
const file = defaultConfigFile();
|
|
844
|
+
if (ctx.mode === "tui" && ctx.hasUI) {
|
|
845
|
+
await openSettingsDialog(ctx, fields, file);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const config = loadProviderConfig(file);
|
|
849
|
+
if (!ctx.hasUI) {
|
|
850
|
+
const text = fields.map((f) => `${f.label}: ${fieldDisplayValue(config, f)}`).join("\n") + `
|
|
851
|
+
Edit ${file} to change.`;
|
|
852
|
+
if (ctx.hasUI) ctx.ui.notify(text, "info");
|
|
853
|
+
else console.log(text);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
let field = fields[0];
|
|
857
|
+
if (!field) return;
|
|
858
|
+
if (fields.length > 1) {
|
|
859
|
+
const labels = fields.map(
|
|
860
|
+
(f) => `${f.label} (current: ${fieldDisplayValue(config, f)})`
|
|
861
|
+
);
|
|
862
|
+
const choice = await ctx.ui.select("Antigravity settings", labels);
|
|
863
|
+
if (!choice) return;
|
|
864
|
+
const found = fields[labels.indexOf(choice)];
|
|
865
|
+
if (!found) return;
|
|
866
|
+
field = found;
|
|
867
|
+
}
|
|
868
|
+
const picked = await ctx.ui.select(
|
|
869
|
+
`${field.label} (current: ${fieldDisplayValue(config, field)})`,
|
|
870
|
+
[...field.options]
|
|
871
|
+
);
|
|
872
|
+
if (!picked || !field.options.includes(picked)) return;
|
|
873
|
+
if (await applySettingValue(field, picked, file, ctx)) {
|
|
874
|
+
ctx.ui.notify(`${field.label} set to ${picked}.`, "info");
|
|
875
|
+
} else {
|
|
876
|
+
ctx.ui.notify(writeFailedMessage(file), "error");
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
async function openSettingsDialog(ctx, fields, file) {
|
|
880
|
+
for (const field of fields) {
|
|
881
|
+
if (field.prepare) await field.prepare(ctx);
|
|
882
|
+
}
|
|
883
|
+
await ctx.ui.custom(async (tui, theme, _kb, done) => {
|
|
884
|
+
const config = loadProviderConfig(file);
|
|
885
|
+
const items = buildSettingsItems(config, fields);
|
|
886
|
+
const paintPreview = (field, value) => {
|
|
887
|
+
const item = items.find((i) => i.id === field.key);
|
|
888
|
+
if (!item) return;
|
|
889
|
+
const base = field.description ?? "";
|
|
890
|
+
const sample = field.renderPreview?.(value, ctx, theme);
|
|
891
|
+
const head = sample && base ? `${base}: ${sample}` : sample ?? base;
|
|
892
|
+
const note = field.optionNotes?.[value];
|
|
893
|
+
item.description = note ? `${head}
|
|
894
|
+
${note}` : head;
|
|
895
|
+
};
|
|
896
|
+
for (const field of fields) {
|
|
897
|
+
const current = fieldDisplayValue(config, field);
|
|
898
|
+
paintPreview(field, current);
|
|
899
|
+
}
|
|
900
|
+
const list = new SettingsList(
|
|
901
|
+
items,
|
|
902
|
+
Math.min(items.length, 10),
|
|
903
|
+
await settingsListTheme(theme),
|
|
904
|
+
(id, newValue) => {
|
|
905
|
+
const field = fields.find((f) => f.key === id);
|
|
906
|
+
if (!field) return;
|
|
907
|
+
list.invalidate();
|
|
908
|
+
tui.requestRender();
|
|
909
|
+
void applySettingValue(field, newValue, file, ctx).then((saved) => {
|
|
910
|
+
if (!saved) {
|
|
911
|
+
list.updateValue(field.key, fieldDisplayValue(loadProviderConfig(file), field));
|
|
912
|
+
ctx.ui.notify(writeFailedMessage(file), "error");
|
|
913
|
+
list.invalidate();
|
|
914
|
+
tui.requestRender();
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
paintPreview(field, newValue);
|
|
918
|
+
list.invalidate();
|
|
919
|
+
tui.requestRender();
|
|
920
|
+
});
|
|
921
|
+
},
|
|
922
|
+
() => done()
|
|
923
|
+
);
|
|
924
|
+
const border = {
|
|
925
|
+
render: (width) => [theme.fg("border", "\u2500".repeat(Math.max(1, width)))]
|
|
926
|
+
};
|
|
927
|
+
return {
|
|
928
|
+
render: (width) => [
|
|
929
|
+
...border.render(width),
|
|
930
|
+
...list.render(width),
|
|
931
|
+
...border.render(width)
|
|
932
|
+
],
|
|
933
|
+
handleInput: (data) => list.handleInput(data),
|
|
934
|
+
invalidate: () => list.invalidate()
|
|
935
|
+
};
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/quota-status.ts
|
|
940
|
+
var FOOTER_MODE_NOTES = Object.freeze({
|
|
941
|
+
smart: "Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage",
|
|
942
|
+
all: "Lists every window of the pool backing the current model (5h or weekly)"
|
|
943
|
+
});
|
|
944
|
+
var FOOTER_MODE_OPTIONS = Object.freeze([
|
|
945
|
+
"off",
|
|
946
|
+
"smart",
|
|
947
|
+
"all"
|
|
948
|
+
]);
|
|
949
|
+
function normalizeFooterMode(value) {
|
|
950
|
+
const v = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
951
|
+
return FOOTER_MODE_OPTIONS.includes(v) ? v : void 0;
|
|
952
|
+
}
|
|
953
|
+
function resolveFooterMode(config) {
|
|
954
|
+
return normalizeFooterMode(config?.settings?.quotaFooter) ?? "off";
|
|
955
|
+
}
|
|
699
956
|
function renderProgressBar(fraction, width = 10) {
|
|
700
957
|
const clamped = Math.max(0, Math.min(1, fraction));
|
|
701
958
|
const filled = Math.round(clamped * width);
|
|
@@ -718,7 +975,9 @@ function shortWindowLabel(bucket, kind) {
|
|
|
718
975
|
return bucket.displayName || bucket.bucketId || "Quota";
|
|
719
976
|
}
|
|
720
977
|
function sortQuotaBuckets(buckets) {
|
|
721
|
-
return buckets.slice().sort(
|
|
978
|
+
return buckets.slice().sort(
|
|
979
|
+
(a, b) => QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)]
|
|
980
|
+
);
|
|
722
981
|
}
|
|
723
982
|
function formatResetRemaining(resetTime) {
|
|
724
983
|
if (!resetTime) return null;
|
|
@@ -769,7 +1028,9 @@ function formatQuotaSummary(summary) {
|
|
|
769
1028
|
const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);
|
|
770
1029
|
const reset = formatResetRemaining(b.resetTime);
|
|
771
1030
|
const suffix = reset && pct < 100 ? ` (${reset})` : "";
|
|
772
|
-
lines.push(
|
|
1031
|
+
lines.push(
|
|
1032
|
+
` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`
|
|
1033
|
+
);
|
|
773
1034
|
}
|
|
774
1035
|
}
|
|
775
1036
|
return lines.join("\n");
|
|
@@ -799,7 +1060,11 @@ function extractWindowFractionPairs(summary) {
|
|
|
799
1060
|
for (const g of summary?.groups || []) {
|
|
800
1061
|
const bucket5h = windowBucket(g.buckets, "5h");
|
|
801
1062
|
const bucketWeekly = windowBucket(g.buckets, "weekly");
|
|
802
|
-
if (bucket5h && bucketWeekly)
|
|
1063
|
+
if (bucket5h && bucketWeekly)
|
|
1064
|
+
pairs[poolKey(bucket5h)] = {
|
|
1065
|
+
"5h": bucket5h.remainingFraction,
|
|
1066
|
+
weekly: bucketWeekly.remainingFraction
|
|
1067
|
+
};
|
|
803
1068
|
}
|
|
804
1069
|
return pairs;
|
|
805
1070
|
}
|
|
@@ -855,25 +1120,79 @@ function formatQuotaWindowPart(bucket) {
|
|
|
855
1120
|
}
|
|
856
1121
|
var ANSI_RED = "\x1B[31m";
|
|
857
1122
|
var ANSI_YELLOW = "\x1B[33m";
|
|
1123
|
+
var ANSI_DIM = "\x1B[90m";
|
|
858
1124
|
var ANSI_FG_RESET = "\x1B[39m";
|
|
859
1125
|
var QUOTA_WARN_PCT = 30;
|
|
860
1126
|
var QUOTA_ALERT_PCT = 10;
|
|
861
|
-
function
|
|
1127
|
+
function resolveColorizer(style) {
|
|
1128
|
+
if (typeof style === "function") return style;
|
|
1129
|
+
if (style && typeof style.fg === "function") return (tone, text) => style.fg(tone, text);
|
|
1130
|
+
return (tone, text) => {
|
|
1131
|
+
const code = tone === "error" ? ANSI_RED : tone === "warning" ? ANSI_YELLOW : ANSI_DIM;
|
|
1132
|
+
return `${code}${text}${ANSI_FG_RESET}`;
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function colorizeQuotaFooter(footer, style) {
|
|
862
1136
|
if (!footer) return void 0;
|
|
1137
|
+
const color = resolveColorizer(style);
|
|
863
1138
|
const match = footer.match(/(\d+)%/);
|
|
864
|
-
if (!match) return footer;
|
|
1139
|
+
if (!match) return color("dim", footer);
|
|
865
1140
|
const pct = parseInt(match[1], 10);
|
|
866
|
-
if (pct <= QUOTA_ALERT_PCT) return
|
|
867
|
-
if (pct <= QUOTA_WARN_PCT) return
|
|
868
|
-
return footer;
|
|
1141
|
+
if (pct <= QUOTA_ALERT_PCT) return color("error", footer);
|
|
1142
|
+
if (pct <= QUOTA_WARN_PCT) return color("warning", footer);
|
|
1143
|
+
return color("dim", footer);
|
|
869
1144
|
}
|
|
870
|
-
function colorizeQuotaFooterBoth(footer) {
|
|
1145
|
+
function colorizeQuotaFooterBoth(footer, style) {
|
|
871
1146
|
if (!footer) return void 0;
|
|
872
|
-
|
|
1147
|
+
const color = resolveColorizer(style);
|
|
1148
|
+
const sep = color("dim", " \xB7 ");
|
|
1149
|
+
return footer.split(" \xB7 ").map((part) => colorizeQuotaFooter(part, style) ?? part).join(sep);
|
|
1150
|
+
}
|
|
1151
|
+
function previewQuotaFooterText(coord, modelId, mode, style) {
|
|
1152
|
+
if (!coord) return void 0;
|
|
1153
|
+
const normalized = normalizeFooterMode(mode);
|
|
1154
|
+
if (!normalized || normalized === "off") return void 0;
|
|
1155
|
+
const { colored } = coord.renderFooter(modelId, normalized, style);
|
|
1156
|
+
if (!colored) return void 0;
|
|
1157
|
+
return ANSI_FG_RESET + colored;
|
|
873
1158
|
}
|
|
874
1159
|
var QUOTA_STATUS_KEY = "pi-provider-antigravity-footer-usage";
|
|
875
1160
|
var QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
876
1161
|
var MAX_OBSERVATION_AGE_MS = 60 * 60 * 1e3;
|
|
1162
|
+
function fileQuotaStatusStore(file = defaultConfigFile()) {
|
|
1163
|
+
return {
|
|
1164
|
+
loadMode: () => resolveFooterMode(loadProviderConfig(file)),
|
|
1165
|
+
loadQuotaState: () => loadSubsystemState("quota", file),
|
|
1166
|
+
saveQuotaState: (state) => saveSubsystemState("quota", state, file)
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
function createQuotaFooterField(quotaStatus) {
|
|
1170
|
+
return {
|
|
1171
|
+
key: "quotaFooter",
|
|
1172
|
+
label: "Quota footer",
|
|
1173
|
+
description: "Show remaining quota in Pi's status footer",
|
|
1174
|
+
options: FOOTER_MODE_OPTIONS,
|
|
1175
|
+
defaultValue: "off",
|
|
1176
|
+
optionNotes: FOOTER_MODE_NOTES,
|
|
1177
|
+
renderPreview: (mode, ctx, theme) => {
|
|
1178
|
+
if (mode === "off") return "hidden";
|
|
1179
|
+
const activeTheme = theme || ctx.ui?.theme;
|
|
1180
|
+
return previewQuotaFooterText(quotaStatus, ctx.model?.id, mode, activeTheme);
|
|
1181
|
+
},
|
|
1182
|
+
prepare: async (ctx) => {
|
|
1183
|
+
await quotaStatus.ensurePreview(ctx);
|
|
1184
|
+
},
|
|
1185
|
+
onChange: async (ctx, _value) => {
|
|
1186
|
+
if (quotaStatus.mode() !== "off") {
|
|
1187
|
+
await quotaStatus.refresh(ctx, { ignoreMode: true });
|
|
1188
|
+
}
|
|
1189
|
+
quotaStatus.paint(ctx);
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
function registerQuotaSettings(quotaStatus) {
|
|
1194
|
+
registerSettingField(createQuotaFooterField(quotaStatus));
|
|
1195
|
+
}
|
|
877
1196
|
function isFiniteNumber(value) {
|
|
878
1197
|
return typeof value === "number" && Number.isFinite(value);
|
|
879
1198
|
}
|
|
@@ -903,261 +1222,23 @@ var FOOTER_MODES = Object.freeze({
|
|
|
903
1222
|
},
|
|
904
1223
|
smart: {
|
|
905
1224
|
key: "smart",
|
|
906
|
-
note:
|
|
907
|
-
render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO) => {
|
|
1225
|
+
note: FOOTER_MODE_NOTES.smart,
|
|
1226
|
+
render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO, style) => {
|
|
908
1227
|
if (!summary) return {};
|
|
909
1228
|
const plain = buildQuotaFooter(summary, modelId, ratio);
|
|
910
|
-
return { plain, colored: colorizeQuotaFooter(plain) };
|
|
1229
|
+
return { plain, colored: colorizeQuotaFooter(plain, style) };
|
|
911
1230
|
}
|
|
912
1231
|
},
|
|
913
1232
|
all: {
|
|
914
1233
|
key: "all",
|
|
915
|
-
note:
|
|
916
|
-
render: (summary, modelId) => {
|
|
1234
|
+
note: FOOTER_MODE_NOTES.all,
|
|
1235
|
+
render: (summary, modelId, _ratio, style) => {
|
|
917
1236
|
if (!summary) return {};
|
|
918
1237
|
const plain = buildQuotaFooterBoth(summary, modelId);
|
|
919
|
-
return { plain, colored: colorizeQuotaFooterBoth(plain) };
|
|
1238
|
+
return { plain, colored: colorizeQuotaFooterBoth(plain, style) };
|
|
920
1239
|
}
|
|
921
1240
|
}
|
|
922
1241
|
});
|
|
923
|
-
var FOOTER_MODE_NOTES = Object.freeze({
|
|
924
|
-
smart: FOOTER_MODES.smart.note,
|
|
925
|
-
all: FOOTER_MODES.all.note
|
|
926
|
-
});
|
|
927
|
-
var FOOTER_MODE_OPTIONS = Object.freeze(
|
|
928
|
-
Object.keys(FOOTER_MODES)
|
|
929
|
-
);
|
|
930
|
-
function normalizeFooterMode(value) {
|
|
931
|
-
const v = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
932
|
-
return v in FOOTER_MODES ? v : void 0;
|
|
933
|
-
}
|
|
934
|
-
var PROVIDER_CONFIG_FILE = "pi-provider-antigravity.json";
|
|
935
|
-
function defaultConfigFile(env = process.env) {
|
|
936
|
-
const dir = (env.PI_CODING_AGENT_DIR || "").trim() || path.join(os.homedir(), ".pi", "agent");
|
|
937
|
-
return path.join(dir, PROVIDER_CONFIG_FILE);
|
|
938
|
-
}
|
|
939
|
-
function loadProviderConfig(file = defaultConfigFile()) {
|
|
940
|
-
try {
|
|
941
|
-
const raw = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
942
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
943
|
-
} catch {
|
|
944
|
-
}
|
|
945
|
-
return void 0;
|
|
946
|
-
}
|
|
947
|
-
function writeProviderConfigAtomically(file, data) {
|
|
948
|
-
const tmp = `${file}.tmp-${process.pid}`;
|
|
949
|
-
try {
|
|
950
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
951
|
-
const mode = fs.existsSync(file) ? fs.statSync(file).mode & 511 : void 0;
|
|
952
|
-
const fd = fs.openSync(tmp, "w", mode);
|
|
953
|
-
try {
|
|
954
|
-
fs.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
955
|
-
fs.fsyncSync(fd);
|
|
956
|
-
} finally {
|
|
957
|
-
fs.closeSync(fd);
|
|
958
|
-
}
|
|
959
|
-
fs.renameSync(tmp, file);
|
|
960
|
-
return true;
|
|
961
|
-
} catch {
|
|
962
|
-
try {
|
|
963
|
-
fs.rmSync(tmp, { force: true });
|
|
964
|
-
} catch {
|
|
965
|
-
}
|
|
966
|
-
return false;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
function updateProviderConfig(file, mutate) {
|
|
970
|
-
try {
|
|
971
|
-
let data = {};
|
|
972
|
-
try {
|
|
973
|
-
const raw = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
974
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
|
|
975
|
-
data = raw;
|
|
976
|
-
} catch (err) {
|
|
977
|
-
if (err?.code !== "ENOENT") return false;
|
|
978
|
-
}
|
|
979
|
-
mutate(data);
|
|
980
|
-
return writeProviderConfigAtomically(file, data);
|
|
981
|
-
} catch {
|
|
982
|
-
return false;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
function resolveFooterMode(config) {
|
|
986
|
-
return normalizeFooterMode(config?.settings?.quotaFooter) ?? "off";
|
|
987
|
-
}
|
|
988
|
-
function fileQuotaStatusStore(file = defaultConfigFile()) {
|
|
989
|
-
return {
|
|
990
|
-
loadMode: () => resolveFooterMode(loadProviderConfig(file)),
|
|
991
|
-
loadQuotaState: () => {
|
|
992
|
-
const quota = loadProviderConfig(file)?.states?.quota;
|
|
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
|
-
})
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
var SETTINGS_FIELDS = Object.freeze([
|
|
1006
|
-
{
|
|
1007
|
-
key: "quotaFooter",
|
|
1008
|
-
label: "Quota footer",
|
|
1009
|
-
description: "Show remaining quota in Pi's status footer",
|
|
1010
|
-
options: FOOTER_MODE_OPTIONS,
|
|
1011
|
-
defaultValue: "off",
|
|
1012
|
-
optionNotes: FOOTER_MODE_NOTES,
|
|
1013
|
-
onChange: async (ctx, quotaStatus) => {
|
|
1014
|
-
if (!quotaStatus) return;
|
|
1015
|
-
if (quotaStatus.mode() !== "off") await quotaStatus.refresh(ctx, { ignoreMode: true });
|
|
1016
|
-
quotaStatus.paint(ctx);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
]);
|
|
1020
|
-
function fieldDisplayValue(config, field) {
|
|
1021
|
-
const raw = config?.settings?.[field.key];
|
|
1022
|
-
if (typeof raw === "string" && raw.trim()) return raw.trim();
|
|
1023
|
-
return field.defaultValue ?? "unset";
|
|
1024
|
-
}
|
|
1025
|
-
function saveSettingValue(field, value, file) {
|
|
1026
|
-
return updateProviderConfig(file, (config) => {
|
|
1027
|
-
const settings = config.settings && typeof config.settings === "object" && !Array.isArray(config.settings) ? config.settings : {};
|
|
1028
|
-
settings[field.key] = value;
|
|
1029
|
-
config.settings = settings;
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
async function applySettingValue(field, value, file, ctx, quotaStatus) {
|
|
1033
|
-
if (!saveSettingValue(field, value, file)) {
|
|
1034
|
-
return false;
|
|
1035
|
-
}
|
|
1036
|
-
await field.onChange?.(ctx, quotaStatus);
|
|
1037
|
-
return true;
|
|
1038
|
-
}
|
|
1039
|
-
function writeFailedMessage(file) {
|
|
1040
|
-
return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;
|
|
1041
|
-
}
|
|
1042
|
-
function previewQuotaFooterText(coord, modelId, mode) {
|
|
1043
|
-
if (!coord) return void 0;
|
|
1044
|
-
const normalized = normalizeFooterMode(mode);
|
|
1045
|
-
if (!normalized || normalized === "off") return void 0;
|
|
1046
|
-
const { colored } = coord.renderFooter(modelId, normalized);
|
|
1047
|
-
if (!colored) return void 0;
|
|
1048
|
-
return ANSI_FG_RESET + colored;
|
|
1049
|
-
}
|
|
1050
|
-
function buildSettingsItems(config, fields = SETTINGS_FIELDS) {
|
|
1051
|
-
return fields.map((f) => ({
|
|
1052
|
-
id: f.key,
|
|
1053
|
-
label: f.label,
|
|
1054
|
-
description: f.description,
|
|
1055
|
-
currentValue: fieldDisplayValue(config, f),
|
|
1056
|
-
values: [...f.options]
|
|
1057
|
-
}));
|
|
1058
|
-
}
|
|
1059
|
-
async function settingsListTheme(theme) {
|
|
1060
|
-
try {
|
|
1061
|
-
const pi = await import("@earendil-works/pi-coding-agent");
|
|
1062
|
-
return pi.getSettingsListTheme();
|
|
1063
|
-
} catch {
|
|
1064
|
-
return {
|
|
1065
|
-
label: (text, selected) => selected ? theme.fg("accent", text) : text,
|
|
1066
|
-
value: (text, selected) => selected ? theme.fg("accent", text) : theme.fg("muted", text),
|
|
1067
|
-
description: (text) => theme.fg("dim", text),
|
|
1068
|
-
cursor: theme.fg("accent", "\u2192 "),
|
|
1069
|
-
hint: (text) => theme.fg("dim", text)
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
async function openSettings(ctx, quotaStatus) {
|
|
1074
|
-
const file = defaultConfigFile();
|
|
1075
|
-
if (ctx.mode === "tui" && ctx.hasUI) {
|
|
1076
|
-
await openSettingsDialog(ctx, quotaStatus, file);
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
const config = loadProviderConfig(file);
|
|
1080
|
-
if (!ctx.hasUI) {
|
|
1081
|
-
const text = SETTINGS_FIELDS.map((f) => `${f.label}: ${fieldDisplayValue(config, f)}`).join("\n") + `
|
|
1082
|
-
Edit ${file} to change.`;
|
|
1083
|
-
if (ctx.hasUI) ctx.ui.notify(text, "info");
|
|
1084
|
-
else console.log(text);
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
let field = SETTINGS_FIELDS[0];
|
|
1088
|
-
if (!field) return;
|
|
1089
|
-
if (SETTINGS_FIELDS.length > 1) {
|
|
1090
|
-
const labels = SETTINGS_FIELDS.map(
|
|
1091
|
-
(f) => `${f.label} (current: ${fieldDisplayValue(config, f)})`
|
|
1092
|
-
);
|
|
1093
|
-
const choice = await ctx.ui.select("Antigravity settings", labels);
|
|
1094
|
-
if (!choice) return;
|
|
1095
|
-
const found = SETTINGS_FIELDS[labels.indexOf(choice)];
|
|
1096
|
-
if (!found) return;
|
|
1097
|
-
field = found;
|
|
1098
|
-
}
|
|
1099
|
-
const picked = await ctx.ui.select(
|
|
1100
|
-
`${field.label} (current: ${fieldDisplayValue(config, field)})`,
|
|
1101
|
-
[...field.options]
|
|
1102
|
-
);
|
|
1103
|
-
if (!picked || !field.options.includes(picked)) return;
|
|
1104
|
-
if (await applySettingValue(field, picked, file, ctx, quotaStatus)) {
|
|
1105
|
-
ctx.ui.notify(`${field.label} set to ${picked}.`, "info");
|
|
1106
|
-
} else {
|
|
1107
|
-
ctx.ui.notify(writeFailedMessage(file), "error");
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
async function openSettingsDialog(ctx, quotaStatus, file) {
|
|
1111
|
-
if (quotaStatus) await quotaStatus.ensurePreview(ctx);
|
|
1112
|
-
await ctx.ui.custom(async (tui, theme, _kb, done) => {
|
|
1113
|
-
const config = loadProviderConfig(file);
|
|
1114
|
-
const items = buildSettingsItems(config);
|
|
1115
|
-
const quotaField = SETTINGS_FIELDS.find((f) => f.key === "quotaFooter");
|
|
1116
|
-
const paintPreview = (mode) => {
|
|
1117
|
-
const item = items.find((i) => i.id === "quotaFooter");
|
|
1118
|
-
if (!item) return;
|
|
1119
|
-
const base = quotaField?.description ?? "";
|
|
1120
|
-
const sample = mode === "off" ? "hidden" : previewQuotaFooterText(quotaStatus, ctx.model?.id, mode);
|
|
1121
|
-
const head = sample && base ? `${base}: ${sample}` : sample ?? base;
|
|
1122
|
-
const note = quotaField?.optionNotes?.[mode];
|
|
1123
|
-
item.description = note ? `${head}
|
|
1124
|
-
${note}` : head;
|
|
1125
|
-
};
|
|
1126
|
-
paintPreview(resolveFooterMode(config));
|
|
1127
|
-
const list = new SettingsList(
|
|
1128
|
-
items,
|
|
1129
|
-
Math.min(items.length, 10),
|
|
1130
|
-
await settingsListTheme(theme),
|
|
1131
|
-
(id, newValue) => {
|
|
1132
|
-
const field = SETTINGS_FIELDS.find((f) => f.key === id);
|
|
1133
|
-
if (!field) return;
|
|
1134
|
-
list.invalidate();
|
|
1135
|
-
tui.requestRender();
|
|
1136
|
-
void applySettingValue(field, newValue, file, ctx, quotaStatus).then((saved) => {
|
|
1137
|
-
if (!saved) {
|
|
1138
|
-
list.updateValue(field.key, fieldDisplayValue(loadProviderConfig(file), field));
|
|
1139
|
-
ctx.ui.notify(writeFailedMessage(file), "error");
|
|
1140
|
-
list.invalidate();
|
|
1141
|
-
tui.requestRender();
|
|
1142
|
-
return;
|
|
1143
|
-
}
|
|
1144
|
-
paintPreview(newValue);
|
|
1145
|
-
list.invalidate();
|
|
1146
|
-
tui.requestRender();
|
|
1147
|
-
});
|
|
1148
|
-
},
|
|
1149
|
-
() => done()
|
|
1150
|
-
);
|
|
1151
|
-
const border = {
|
|
1152
|
-
render: (width) => [theme.fg("border", "\u2500".repeat(Math.max(1, width)))]
|
|
1153
|
-
};
|
|
1154
|
-
return {
|
|
1155
|
-
render: (width) => [...border.render(width), ...list.render(width), ...border.render(width)],
|
|
1156
|
-
handleInput: (data) => list.handleInput(data),
|
|
1157
|
-
invalidate: () => list.invalidate()
|
|
1158
|
-
};
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
1242
|
function isAntigravityModel(model) {
|
|
1162
1243
|
return model?.provider === PROVIDER_ID;
|
|
1163
1244
|
}
|
|
@@ -1187,8 +1268,8 @@ var QuotaStatusCoordinator = class {
|
|
|
1187
1268
|
footerFor(modelId, mode = "smart") {
|
|
1188
1269
|
return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio).plain;
|
|
1189
1270
|
}
|
|
1190
|
-
renderFooter(modelId, mode = "smart") {
|
|
1191
|
-
return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio) ?? {};
|
|
1271
|
+
renderFooter(modelId, mode = "smart", style) {
|
|
1272
|
+
return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, style) ?? {};
|
|
1192
1273
|
}
|
|
1193
1274
|
get isFresh() {
|
|
1194
1275
|
return !!this.summary && Date.now() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;
|
|
@@ -1218,7 +1299,8 @@ var QuotaStatusCoordinator = class {
|
|
|
1218
1299
|
ctx.ui.setStatus(QUOTA_STATUS_KEY, void 0);
|
|
1219
1300
|
return;
|
|
1220
1301
|
}
|
|
1221
|
-
const
|
|
1302
|
+
const theme = ctx.ui?.theme;
|
|
1303
|
+
const { colored } = this.renderFooter(ctx.model?.id, mode, theme);
|
|
1222
1304
|
ctx.ui.setStatus(QUOTA_STATUS_KEY, colored);
|
|
1223
1305
|
}
|
|
1224
1306
|
// Atomic inspect for /antigravity usage: forces fresh quota fetch, repaints
|
|
@@ -1231,10 +1313,6 @@ var QuotaStatusCoordinator = class {
|
|
|
1231
1313
|
this.paint(ctx);
|
|
1232
1314
|
return formatQuotaSummary(summary);
|
|
1233
1315
|
}
|
|
1234
|
-
// Opens provider settings dialog/menu and updates state atomically.
|
|
1235
|
-
async openSettings(ctx) {
|
|
1236
|
-
await openSettings(ctx, this);
|
|
1237
|
-
}
|
|
1238
1316
|
// One fetch for preview purposes even when the footer slot is off or the
|
|
1239
1317
|
// current model belongs to another provider: opening quota settings is an
|
|
1240
1318
|
// explicit look at quota (like /antigravity usage), and the preview is the
|
|
@@ -1270,7 +1348,11 @@ var QuotaStatusCoordinator = class {
|
|
|
1270
1348
|
// Accepts an externally fetched summary into the shared cache: same
|
|
1271
1349
|
// calibration, persistence, and freshness as fetch().
|
|
1272
1350
|
ingest(summary) {
|
|
1273
|
-
const calibrated = calibrateWeeklyTo5hRatio(
|
|
1351
|
+
const calibrated = calibrateWeeklyTo5hRatio(
|
|
1352
|
+
this.lastPairs,
|
|
1353
|
+
extractWindowFractionPairs(summary),
|
|
1354
|
+
this.weeklyTo5hRatio
|
|
1355
|
+
);
|
|
1274
1356
|
this.weeklyTo5hRatio = calibrated;
|
|
1275
1357
|
this.lastPairs = extractWindowFractionPairs(summary);
|
|
1276
1358
|
this.summary = summary;
|
|
@@ -2031,7 +2113,10 @@ var SUBCOMMANDS = Object.freeze([
|
|
|
2031
2113
|
name: "settings",
|
|
2032
2114
|
description: "Configure provider settings",
|
|
2033
2115
|
aliases: Object.freeze(["setting"]),
|
|
2034
|
-
run: async ({ ctx, quotaStatus }) =>
|
|
2116
|
+
run: async ({ ctx, quotaStatus }) => {
|
|
2117
|
+
if (quotaStatus) registerQuotaSettings(quotaStatus);
|
|
2118
|
+
await openSettings(ctx);
|
|
2119
|
+
}
|
|
2035
2120
|
},
|
|
2036
2121
|
{
|
|
2037
2122
|
name: "websearch",
|
|
@@ -2182,6 +2267,7 @@ async function runAntigravitySubcommand(args, ctx, quotaStatus, catalog) {
|
|
|
2182
2267
|
var PROVIDER_NAME = "Antigravity";
|
|
2183
2268
|
function index_default(pi) {
|
|
2184
2269
|
const quotaStatus = new QuotaStatusCoordinator();
|
|
2270
|
+
registerQuotaSettings(quotaStatus);
|
|
2185
2271
|
const catalog = createModelCatalog();
|
|
2186
2272
|
pi.registerProvider(PROVIDER_ID, {
|
|
2187
2273
|
name: PROVIDER_NAME,
|