pi-provider-antigravity 0.12.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 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.4 (aidev_client; os_type=linux; arch=amd64; cl=982146307; auth_method=consumer)";
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/models.ts
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) => [
@@ -696,21 +696,7 @@ 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 ANSI_FG_RESET = "\x1B[39m";
700
699
  var PROVIDER_CONFIG_FILE = "pi-provider-antigravity.json";
701
- var FOOTER_MODE_NOTES = Object.freeze({
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)"
704
- });
705
- var FOOTER_MODE_OPTIONS = Object.freeze([
706
- "off",
707
- "smart",
708
- "all"
709
- ]);
710
- function normalizeFooterMode(value) {
711
- const v = typeof value === "string" ? value.trim().toLowerCase() : "";
712
- return FOOTER_MODE_OPTIONS.includes(v) ? v : void 0;
713
- }
714
700
  function defaultConfigFile(env = process.env) {
715
701
  const dir = (env.PI_CODING_AGENT_DIR || "").trim() || path.join(os.homedir(), ".pi", "agent");
716
702
  return path.join(dir, PROVIDER_CONFIG_FILE);
@@ -761,9 +747,6 @@ function updateProviderConfig(file, mutate) {
761
747
  return false;
762
748
  }
763
749
  }
764
- function resolveFooterMode(config) {
765
- return normalizeFooterMode(config?.settings?.quotaFooter) ?? "off";
766
- }
767
750
  function loadSubsystemState(subsystem, file = defaultConfigFile()) {
768
751
  const state = loadProviderConfig(file)?.states?.[subsystem];
769
752
  return state && typeof state === "object" && !Array.isArray(state) ? state : void 0;
@@ -776,28 +759,33 @@ function saveSubsystemState(subsystem, state, file = defaultConfigFile()) {
776
759
  config.states = states;
777
760
  });
778
761
  }
779
- function fileQuotaStatusStore(file = defaultConfigFile()) {
780
- return {
781
- loadMode: () => resolveFooterMode(loadProviderConfig(file)),
782
- loadQuotaState: () => loadSubsystemState("quota", file),
783
- saveQuotaState: (state) => saveSubsystemState("quota", state, file)
784
- };
785
- }
786
- var SETTINGS_FIELDS = Object.freeze([
787
- {
788
- key: "quotaFooter",
789
- label: "Quota footer",
790
- description: "Show remaining quota in Pi's status footer",
791
- options: FOOTER_MODE_OPTIONS,
792
- defaultValue: "off",
793
- optionNotes: FOOTER_MODE_NOTES,
794
- onChange: async (ctx, quotaStatus) => {
795
- if (!quotaStatus) return;
796
- if (quotaStatus.mode() !== "off") await quotaStatus.refresh(ctx, { ignoreMode: true });
797
- quotaStatus.paint(ctx);
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);
798
770
  }
799
771
  }
800
- ]);
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
+ }
801
789
  function fieldDisplayValue(config, field) {
802
790
  const raw = config?.settings?.[field.key];
803
791
  if (typeof raw === "string" && raw.trim()) return raw.trim();
@@ -810,25 +798,17 @@ function saveSettingValue(field, value, file) {
810
798
  config.settings = settings;
811
799
  });
812
800
  }
813
- async function applySettingValue(field, value, file, ctx, quotaStatus) {
801
+ async function applySettingValue(field, value, file, ctx) {
814
802
  if (!saveSettingValue(field, value, file)) {
815
803
  return false;
816
804
  }
817
- await field.onChange?.(ctx, quotaStatus);
805
+ await field.onChange?.(ctx, value);
818
806
  return true;
819
807
  }
820
808
  function writeFailedMessage(file) {
821
809
  return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;
822
810
  }
823
- function previewQuotaFooterText(coord, modelId, mode, style) {
824
- if (!coord) return void 0;
825
- const normalized = normalizeFooterMode(mode);
826
- if (!normalized || normalized === "off") return void 0;
827
- const { colored } = coord.renderFooter(modelId, normalized, style);
828
- if (!colored) return void 0;
829
- return ANSI_FG_RESET + colored;
830
- }
831
- function buildSettingsItems(config, fields = SETTINGS_FIELDS) {
811
+ function buildSettingsItems(config, fields = getRegisteredSettingFields()) {
832
812
  return fields.map((f) => ({
833
813
  id: f.key,
834
814
  label: f.label,
@@ -851,29 +831,37 @@ async function settingsListTheme(theme) {
851
831
  };
852
832
  }
853
833
  }
854
- async function openSettings(ctx, quotaStatus) {
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);
855
843
  const file = defaultConfigFile();
856
844
  if (ctx.mode === "tui" && ctx.hasUI) {
857
- await openSettingsDialog(ctx, quotaStatus, file);
845
+ await openSettingsDialog(ctx, fields, file);
858
846
  return;
859
847
  }
860
848
  const config = loadProviderConfig(file);
861
849
  if (!ctx.hasUI) {
862
- const text = SETTINGS_FIELDS.map((f) => `${f.label}: ${fieldDisplayValue(config, f)}`).join("\n") + `
850
+ const text = fields.map((f) => `${f.label}: ${fieldDisplayValue(config, f)}`).join("\n") + `
863
851
  Edit ${file} to change.`;
864
852
  if (ctx.hasUI) ctx.ui.notify(text, "info");
865
853
  else console.log(text);
866
854
  return;
867
855
  }
868
- let field = SETTINGS_FIELDS[0];
856
+ let field = fields[0];
869
857
  if (!field) return;
870
- if (SETTINGS_FIELDS.length > 1) {
871
- const labels = SETTINGS_FIELDS.map(
858
+ if (fields.length > 1) {
859
+ const labels = fields.map(
872
860
  (f) => `${f.label} (current: ${fieldDisplayValue(config, f)})`
873
861
  );
874
862
  const choice = await ctx.ui.select("Antigravity settings", labels);
875
863
  if (!choice) return;
876
- const found = SETTINGS_FIELDS[labels.indexOf(choice)];
864
+ const found = fields[labels.indexOf(choice)];
877
865
  if (!found) return;
878
866
  field = found;
879
867
  }
@@ -882,39 +870,43 @@ Edit ${file} to change.`;
882
870
  [...field.options]
883
871
  );
884
872
  if (!picked || !field.options.includes(picked)) return;
885
- if (await applySettingValue(field, picked, file, ctx, quotaStatus)) {
873
+ if (await applySettingValue(field, picked, file, ctx)) {
886
874
  ctx.ui.notify(`${field.label} set to ${picked}.`, "info");
887
875
  } else {
888
876
  ctx.ui.notify(writeFailedMessage(file), "error");
889
877
  }
890
878
  }
891
- async function openSettingsDialog(ctx, quotaStatus, file) {
892
- if (quotaStatus) await quotaStatus.ensurePreview(ctx);
879
+ async function openSettingsDialog(ctx, fields, file) {
880
+ for (const field of fields) {
881
+ if (field.prepare) await field.prepare(ctx);
882
+ }
893
883
  await ctx.ui.custom(async (tui, theme, _kb, done) => {
894
884
  const config = loadProviderConfig(file);
895
- const items = buildSettingsItems(config);
896
- const quotaField = SETTINGS_FIELDS.find((f) => f.key === "quotaFooter");
897
- const paintPreview = (mode) => {
898
- const item = items.find((i) => i.id === "quotaFooter");
885
+ const items = buildSettingsItems(config, fields);
886
+ const paintPreview = (field, value) => {
887
+ const item = items.find((i) => i.id === field.key);
899
888
  if (!item) return;
900
- const base = quotaField?.description ?? "";
901
- const sample = mode === "off" ? "hidden" : previewQuotaFooterText(quotaStatus, ctx.model?.id, mode, theme);
889
+ const base = field.description ?? "";
890
+ const sample = field.renderPreview?.(value, ctx, theme);
902
891
  const head = sample && base ? `${base}: ${sample}` : sample ?? base;
903
- const note = quotaField?.optionNotes?.[mode];
892
+ const note = field.optionNotes?.[value];
904
893
  item.description = note ? `${head}
905
894
  ${note}` : head;
906
895
  };
907
- paintPreview(resolveFooterMode(config));
896
+ for (const field of fields) {
897
+ const current = fieldDisplayValue(config, field);
898
+ paintPreview(field, current);
899
+ }
908
900
  const list = new SettingsList(
909
901
  items,
910
902
  Math.min(items.length, 10),
911
903
  await settingsListTheme(theme),
912
904
  (id, newValue) => {
913
- const field = SETTINGS_FIELDS.find((f) => f.key === id);
905
+ const field = fields.find((f) => f.key === id);
914
906
  if (!field) return;
915
907
  list.invalidate();
916
908
  tui.requestRender();
917
- void applySettingValue(field, newValue, file, ctx, quotaStatus).then((saved) => {
909
+ void applySettingValue(field, newValue, file, ctx).then((saved) => {
918
910
  if (!saved) {
919
911
  list.updateValue(field.key, fieldDisplayValue(loadProviderConfig(file), field));
920
912
  ctx.ui.notify(writeFailedMessage(file), "error");
@@ -922,7 +914,7 @@ ${note}` : head;
922
914
  tui.requestRender();
923
915
  return;
924
916
  }
925
- paintPreview(newValue);
917
+ paintPreview(field, newValue);
926
918
  list.invalidate();
927
919
  tui.requestRender();
928
920
  });
@@ -945,6 +937,22 @@ ${note}` : head;
945
937
  }
946
938
 
947
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
+ }
948
956
  function renderProgressBar(fraction, width = 10) {
949
957
  const clamped = Math.max(0, Math.min(1, fraction));
950
958
  const filled = Math.round(clamped * width);
@@ -1113,7 +1121,7 @@ function formatQuotaWindowPart(bucket) {
1113
1121
  var ANSI_RED = "\x1B[31m";
1114
1122
  var ANSI_YELLOW = "\x1B[33m";
1115
1123
  var ANSI_DIM = "\x1B[90m";
1116
- var ANSI_FG_RESET2 = "\x1B[39m";
1124
+ var ANSI_FG_RESET = "\x1B[39m";
1117
1125
  var QUOTA_WARN_PCT = 30;
1118
1126
  var QUOTA_ALERT_PCT = 10;
1119
1127
  function resolveColorizer(style) {
@@ -1121,7 +1129,7 @@ function resolveColorizer(style) {
1121
1129
  if (style && typeof style.fg === "function") return (tone, text) => style.fg(tone, text);
1122
1130
  return (tone, text) => {
1123
1131
  const code = tone === "error" ? ANSI_RED : tone === "warning" ? ANSI_YELLOW : ANSI_DIM;
1124
- return `${code}${text}${ANSI_FG_RESET2}`;
1132
+ return `${code}${text}${ANSI_FG_RESET}`;
1125
1133
  };
1126
1134
  }
1127
1135
  function colorizeQuotaFooter(footer, style) {
@@ -1140,9 +1148,51 @@ function colorizeQuotaFooterBoth(footer, style) {
1140
1148
  const sep = color("dim", " \xB7 ");
1141
1149
  return footer.split(" \xB7 ").map((part) => colorizeQuotaFooter(part, style) ?? part).join(sep);
1142
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;
1158
+ }
1143
1159
  var QUOTA_STATUS_KEY = "pi-provider-antigravity-footer-usage";
1144
1160
  var QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
1145
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
+ }
1146
1196
  function isFiniteNumber(value) {
1147
1197
  return typeof value === "number" && Number.isFinite(value);
1148
1198
  }
@@ -2063,7 +2113,10 @@ var SUBCOMMANDS = Object.freeze([
2063
2113
  name: "settings",
2064
2114
  description: "Configure provider settings",
2065
2115
  aliases: Object.freeze(["setting"]),
2066
- run: async ({ ctx, quotaStatus }) => openSettings(ctx, quotaStatus)
2116
+ run: async ({ ctx, quotaStatus }) => {
2117
+ if (quotaStatus) registerQuotaSettings(quotaStatus);
2118
+ await openSettings(ctx);
2119
+ }
2067
2120
  },
2068
2121
  {
2069
2122
  name: "websearch",
@@ -2214,6 +2267,7 @@ async function runAntigravitySubcommand(args, ctx, quotaStatus, catalog) {
2214
2267
  var PROVIDER_NAME = "Antigravity";
2215
2268
  function index_default(pi) {
2216
2269
  const quotaStatus = new QuotaStatusCoordinator();
2270
+ registerQuotaSettings(quotaStatus);
2217
2271
  const catalog = createModelCatalog();
2218
2272
  pi.registerProvider(PROVIDER_ID, {
2219
2273
  name: PROVIDER_NAME,