pi-provider-antigravity 0.6.0 → 0.7.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
@@ -3,7 +3,7 @@ import { createHash, randomBytes } from "node:crypto";
3
3
 
4
4
  // src/protocol.ts
5
5
  var DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
6
- var DEFAULT_USER_AGENT = "antigravity/cli/1.1.28 (aidev_client; os_type=linux; arch=amd64; cl=978129418; auth_method=consumer)";
6
+ var DEFAULT_USER_AGENT = "antigravity/cli/1.2.0 (aidev_client; os_type=linux; arch=amd64; cl=978750357; auth_method=consumer)";
7
7
  var PROVIDER_ID = "antigravity";
8
8
  async function postAntigravity({
9
9
  token,
@@ -187,10 +187,34 @@ function parseStoredCredentials(rawApiKey) {
187
187
  }
188
188
 
189
189
  // src/model-catalog.ts
190
+ var CANONICAL_TIER_SUFFIXES = Object.freeze({
191
+ low: "-low",
192
+ medium: "-medium",
193
+ high: "-high"
194
+ });
195
+ var TIER_ALIASES = Object.freeze({
196
+ low: Object.freeze(["-extra-low"]),
197
+ medium: Object.freeze([]),
198
+ high: Object.freeze(["-thinking", "-agent"])
199
+ });
200
+ var SPECIAL_TIER_SUFFIXES = Object.freeze(["-tiered"]);
201
+ var ALL_TIER_SUFFIXES = Object.freeze([
202
+ .../* @__PURE__ */ new Set([
203
+ ...Object.values(CANONICAL_TIER_SUFFIXES),
204
+ ...Object.values(TIER_ALIASES).flat(),
205
+ ...SPECIAL_TIER_SUFFIXES
206
+ ])
207
+ ]);
208
+ var TIER_SUFFIX_PATTERN = new RegExp(
209
+ `-(?:${ALL_TIER_SUFFIXES.map((s) => s.replace(/^-/, "")).join("|")})$`
210
+ );
211
+ function tierSpellings(tier) {
212
+ return [CANONICAL_TIER_SUFFIXES[tier], ...TIER_ALIASES[tier]];
213
+ }
190
214
  function extractBaseModelId(runtimeId) {
191
215
  if (runtimeId === "gemini-pro-agent") return "gemini-3.1-pro";
192
216
  if (runtimeId.startsWith("gemini-3.1-pro-")) return "gemini-3.1-pro";
193
- return runtimeId.replace(/-(?:high|medium|low|tiered|thinking|agent|extra-low)$/, "");
217
+ return runtimeId.replace(TIER_SUFFIX_PATTERN, "");
194
218
  }
195
219
  function classifyModelFamily(modelId) {
196
220
  const bare = ((modelId || "").split("/").pop() || "").toLowerCase();
@@ -206,37 +230,6 @@ function isCompatibleFamily(msgModel, targetModelId) {
206
230
  if (msgFamily !== "unknown" && msgFamily === classifyModelFamily(targetModelId)) return true;
207
231
  return extractBaseModelId(msgModel) === extractBaseModelId(targetModelId);
208
232
  }
209
- function getThinkingConfig(modelId, effort) {
210
- const isOff = effort === "off" || effort === "none";
211
- if (isOff) {
212
- return { includeThoughts: false, thinkingBudget: 0 };
213
- }
214
- if (modelId.includes("flash")) {
215
- if (effort === "high" || effort === "xhigh" || modelId.endsWith("-high") || modelId.endsWith("-agent")) {
216
- return { includeThoughts: true, thinkingBudget: -1 };
217
- }
218
- if (effort === "medium" || modelId.endsWith("-medium")) {
219
- return { includeThoughts: true, thinkingBudget: 4e3 };
220
- }
221
- if (effort === "low" || effort === "minimal" || modelId.endsWith("-low")) {
222
- return { includeThoughts: true, thinkingBudget: 1e3 };
223
- }
224
- return { includeThoughts: true, thinkingBudget: -1 };
225
- }
226
- if (modelId.includes("pro")) {
227
- if (effort === "low" || effort === "minimal" || modelId.endsWith("-low")) {
228
- return { includeThoughts: true, thinkingBudget: 1001 };
229
- }
230
- return { includeThoughts: true, thinkingBudget: 10001 };
231
- }
232
- if (modelId.startsWith("claude-")) {
233
- return { includeThoughts: true, thinkingBudget: 1024 };
234
- }
235
- if (modelId.startsWith("gpt-oss-")) {
236
- return { includeThoughts: true, thinkingBudget: 8192 };
237
- }
238
- return { includeThoughts: true, thinkingBudget: -1 };
239
- }
240
233
  function buildThinkingMap(models) {
241
234
  return Object.fromEntries(
242
235
  models.map((m) => [
@@ -248,87 +241,110 @@ function buildThinkingMap(models) {
248
241
  ])
249
242
  );
250
243
  }
251
- var activeStore = {
252
- enums: {},
253
- runtimeIds: [],
254
- thinking: {},
255
- deprecated: {},
256
- version: 0
257
- };
258
- function getCatalogSnapshot() {
244
+ function snapshotFromCatalog(catalog) {
259
245
  return {
260
- enums: { ...activeStore.enums },
261
- runtimeIds: [...activeStore.runtimeIds],
246
+ enums: { ...catalog.modelEnums },
247
+ runtimeIds: [...new Set(catalog.models.map((m) => m.id))],
248
+ thinking: buildThinkingMap(catalog.models),
249
+ deprecated: { ...catalog.deprecated ?? {} }
250
+ };
251
+ }
252
+ function copySnapshot(snapshot) {
253
+ return {
254
+ enums: { ...snapshot.enums },
255
+ runtimeIds: [...snapshot.runtimeIds],
262
256
  thinking: Object.fromEntries(
263
- Object.entries(activeStore.thinking ?? {}).map(([id, info]) => [id, { ...info }])
257
+ Object.entries(snapshot.thinking).map(([id, info]) => [id, { ...info }])
264
258
  ),
265
- deprecated: { ...activeStore.deprecated ?? {} },
266
- version: activeStore.version
259
+ deprecated: { ...snapshot.deprecated }
267
260
  };
268
261
  }
269
- function updateCatalogStore(enums, runtimeIds, thinking = {}, deprecated = {}) {
270
- activeStore = {
271
- enums: { ...enums },
272
- runtimeIds: [...new Set(runtimeIds)],
273
- thinking: { ...thinking },
274
- deprecated: { ...deprecated },
275
- version: activeStore.version + 1
262
+ var EMPTY_SNAPSHOT = () => ({ enums: {}, runtimeIds: [], thinking: {}, deprecated: {} });
263
+ function createCatalogStore() {
264
+ let generation = { snapshot: EMPTY_SNAPSHOT(), version: 0 };
265
+ return {
266
+ generation: () => ({ ...generation, snapshot: copySnapshot(generation.snapshot) }),
267
+ record: (catalog) => {
268
+ generation = {
269
+ snapshot: snapshotFromCatalog(catalog),
270
+ items: catalog,
271
+ version: generation.version + 1
272
+ };
273
+ },
274
+ // Version 0 means no fetch has landed in this process, so nothing here can
275
+ // be fresher than the persist: an older file must never replace a recorded
276
+ // generation (the refresh path calls this on every start, including after
277
+ // a successful fetch in the same process).
278
+ restore: (snapshot) => {
279
+ if (generation.version > 0) return;
280
+ generation = { snapshot: copySnapshot(snapshot), version: 0 };
281
+ }
276
282
  };
277
283
  }
278
- var lastCatalog;
279
- function ingestCatalog(catalog) {
280
- updateCatalogStore(
281
- catalog.modelEnums,
282
- catalog.models.map((m) => m.id),
283
- buildThinkingMap(catalog.models),
284
- catalog.deprecated || {}
285
- );
286
- lastCatalog = catalog;
284
+ function toPersistedSnapshot(snapshot) {
285
+ return {
286
+ modelEnums: { ...snapshot.enums },
287
+ runtimeIds: [...snapshot.runtimeIds],
288
+ thinking: { ...snapshot.thinking },
289
+ deprecated: { ...snapshot.deprecated }
290
+ };
291
+ }
292
+ function isStringRecord(value) {
293
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
287
294
  }
288
- function getStoredCatalog() {
289
- return lastCatalog;
295
+ function isThinkingRecord(value) {
296
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => !!entry && typeof entry === "object" && !Array.isArray(entry));
290
297
  }
291
- function resolveRuntimeModelId(modelId, effort, availableRuntimeIds = activeStore.runtimeIds) {
292
- if (modelId.endsWith("-high") || modelId.endsWith("-medium") || modelId.endsWith("-low") || modelId.endsWith("-thinking") || modelId.endsWith("-agent") || modelId.endsWith("-extra-low") || modelId.endsWith("-tiered")) {
298
+ function fromPersistedSnapshot(raw) {
299
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
300
+ const entry = raw;
301
+ const enums = isStringRecord(entry.modelEnums) ? entry.modelEnums : void 0;
302
+ const runtimeIds = Array.isArray(entry.runtimeIds) ? entry.runtimeIds.filter((id) => typeof id === "string") : void 0;
303
+ const thinking = isThinkingRecord(entry.thinking) ? entry.thinking : void 0;
304
+ const deprecated = isStringRecord(entry.deprecated) ? entry.deprecated : void 0;
305
+ if (!enums && !runtimeIds && !thinking && !deprecated) return void 0;
306
+ return { enums: enums ?? {}, runtimeIds: runtimeIds ?? [], thinking: thinking ?? {}, deprecated: deprecated ?? {} };
307
+ }
308
+ var TIER_FALLBACKS = Object.freeze({
309
+ minimal: Object.freeze(["-low", "-extra-low", ""]),
310
+ low: Object.freeze(["-extra-low", ""]),
311
+ medium: Object.freeze(["", "-high"]),
312
+ // Gemini 3.1 Pro lists no -medium: up to high, never down
313
+ high: Object.freeze(["-thinking", "-agent", ""]),
314
+ // Claude's high tier is -thinking
315
+ xhigh: Object.freeze(["-high", "-thinking", "-agent", ""]),
316
+ max: Object.freeze(["-high", "-thinking", "-agent", ""])
317
+ });
318
+ var DEFAULT_TIER_ORDER = Object.freeze([
319
+ CANONICAL_TIER_SUFFIXES.high,
320
+ ...TIER_ALIASES.high,
321
+ ""
322
+ ]);
323
+ function tierCandidateOrder(effort) {
324
+ if (!effort) return DEFAULT_TIER_ORDER;
325
+ const canonical = effort in CANONICAL_TIER_SUFFIXES ? [CANONICAL_TIER_SUFFIXES[effort]] : [`-${effort}`];
326
+ const fallbacks = TIER_FALLBACKS[effort] ?? DEFAULT_TIER_ORDER;
327
+ return [.../* @__PURE__ */ new Set([...canonical, ...fallbacks])];
328
+ }
329
+ function resolveRuntimeModelId(modelId, effort, availableRuntimeIds) {
330
+ if (ALL_TIER_SUFFIXES.some((suffix) => modelId.endsWith(suffix))) {
293
331
  return modelId;
294
332
  }
295
- const isOff = effort === "off" || effort === "none";
296
- const isLow = isOff || effort === "low" || effort === "minimal";
297
- const isMedium = effort === "medium";
298
- if (Array.isArray(availableRuntimeIds) && availableRuntimeIds.length > 0) {
299
- const candidates = [];
300
- if (isLow) {
301
- candidates.push(`${modelId}-low`, `${modelId}-extra-low`, modelId);
302
- } else if (isMedium) {
303
- candidates.push(`${modelId}-medium`, modelId);
304
- } else {
305
- candidates.push(`${modelId}-high`, `${modelId}-thinking`, `${modelId}-agent`, modelId);
306
- }
307
- for (const cand of candidates) {
308
- if (availableRuntimeIds.includes(cand)) {
309
- return cand;
333
+ if (availableRuntimeIds.length > 0) {
334
+ const variants = new Set(availableRuntimeIds.filter((id) => extractBaseModelId(id) === modelId));
335
+ const order = tierCandidateOrder(effort);
336
+ for (const suffix of order) {
337
+ if (variants.has(`${modelId}${suffix}`)) {
338
+ return `${modelId}${suffix}`;
310
339
  }
311
340
  }
312
- }
313
- if (modelId.includes("flash")) {
314
- if (isLow) return `${modelId}-low`;
315
- if (isMedium) return `${modelId}-medium`;
316
- return `${modelId}-high`;
317
- }
318
- if (modelId.includes("pro")) {
319
- if (isLow) return `${modelId}-low`;
320
- return `${modelId}-high`;
321
- }
322
- if (modelId.startsWith("claude-")) {
323
- if (modelId.includes("opus")) return `${modelId}-thinking`;
324
- return modelId;
325
- }
326
- if (modelId.startsWith("gpt-")) {
327
- return `${modelId}-medium`;
341
+ if (variants.size === 1) {
342
+ return [...variants][0];
343
+ }
328
344
  }
329
345
  return modelId;
330
346
  }
331
- function resolveModelPlan(publicModelId, effort, snapshot = getCatalogSnapshot()) {
347
+ function resolveModelPlan(publicModelId, effort, snapshot) {
332
348
  const runtimeModelId = followRenames(
333
349
  resolveRuntimeModelId(publicModelId, effort, snapshot.runtimeIds),
334
350
  snapshot
@@ -336,14 +352,14 @@ function resolveModelPlan(publicModelId, effort, snapshot = getCatalogSnapshot()
336
352
  const modelEnum = snapshot.enums[runtimeModelId];
337
353
  if (!modelEnum) {
338
354
  throw new Error(
339
- `Unknown model "${runtimeModelId}" (not in catalog snapshot v${snapshot.version}). Run /antigravity refresh and pick a current model.`
355
+ `Unknown model "${runtimeModelId}" (not in the current catalog snapshot). Run /antigravity refresh and pick a current model.`
340
356
  );
341
357
  }
342
358
  const family = classifyModelFamily(runtimeModelId);
343
359
  return {
344
360
  runtimeModelId,
345
361
  modelEnum,
346
- thinkingConfig: resolveThinkingConfig(runtimeModelId, effort, snapshot),
362
+ thinkingConfig: resolveThinkingConfig(runtimeModelId, snapshot),
347
363
  isNonGemini: family !== "gemini",
348
364
  isClaude: family === "claude"
349
365
  };
@@ -359,29 +375,27 @@ function followRenames(runtimeModelId, snapshot) {
359
375
  }
360
376
  return current;
361
377
  }
362
- function resolveThinkingConfig(runtimeModelId, effort, snapshot = getCatalogSnapshot()) {
363
- if (effort === "off" || effort === "none") {
364
- return { includeThoughts: false, thinkingBudget: 0 };
365
- }
366
- const info = snapshot.thinking?.[runtimeModelId];
367
- if (info) {
368
- if (typeof info.budget === "number") {
369
- return { includeThoughts: true, thinkingBudget: info.budget };
370
- }
371
- if (!info.supportsThinking) {
372
- return { includeThoughts: false, thinkingBudget: 0 };
373
- }
374
- }
375
- return getThinkingConfig(runtimeModelId, effort);
378
+ function resolveThinkingConfig(runtimeModelId, snapshot) {
379
+ const budget = snapshot.thinking[runtimeModelId]?.budget;
380
+ return typeof budget === "number" ? { includeThoughts: true, thinkingBudget: budget } : { includeThoughts: false, thinkingBudget: 0 };
376
381
  }
377
382
  function estimateModelCost(_baseId) {
378
383
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
379
384
  }
380
385
  function synthesizeDynamicModel(baseId, items) {
381
386
  const repItem = items.find((it) => it.id === `${baseId}-high` || it.id === baseId) || items[0];
387
+ const hasVariant = (suffixes) => items.some((it) => suffixes.some((suffix) => it.id.endsWith(suffix)));
388
+ const thinkingLevelMap = {
389
+ off: null,
390
+ minimal: null,
391
+ ...hasVariant(tierSpellings("low")) ? {} : { low: null },
392
+ ...hasVariant(tierSpellings("medium")) ? {} : { medium: null },
393
+ ...hasVariant(tierSpellings("high")) || items.some((it) => it.id === baseId) ? {} : { high: null }
394
+ };
395
+ const family = classifyModelFamily(baseId);
382
396
  const isFlash = baseId.includes("flash");
383
- const isClaude = baseId.startsWith("claude-");
384
- const isGpt = baseId.startsWith("gpt-");
397
+ const isClaude = family === "claude";
398
+ const isGpt = family === "gpt";
385
399
  const defaultContext = isFlash ? 1048576 : isClaude ? 25e4 : isGpt ? 128e3 : 1048576;
386
400
  const defaultMaxOutput = isClaude ? 64e3 : isGpt ? 32768 : 65536;
387
401
  return {
@@ -391,6 +405,7 @@ function synthesizeDynamicModel(baseId, items) {
391
405
  api: "antigravity-api",
392
406
  baseUrl: DEFAULT_ENDPOINT,
393
407
  reasoning: repItem?.supportsThinking ?? true,
408
+ thinkingLevelMap,
394
409
  input: repItem?.supportsImages ? ["text", "image"] : ["text"],
395
410
  cost: estimateModelCost(baseId),
396
411
  contextWindow: repItem?.maxTokens || defaultContext,
@@ -485,53 +500,44 @@ function formatModelsList(catalog) {
485
500
  }
486
501
 
487
502
  // src/catalog-refresh.ts
488
- async function refreshCatalog(context) {
489
- const storedEnums = context.stored?.["pi-provider-antigravity"]?.modelEnums;
490
- const storedRuntimeIds = context.stored?.["pi-provider-antigravity"]?.runtimeIds;
491
- const storedThinking = context.stored?.["pi-provider-antigravity"]?.thinking;
492
- const storedDeprecated = context.stored?.["pi-provider-antigravity"]?.deprecated;
493
- if ((storedEnums || storedRuntimeIds || storedThinking || storedDeprecated) && getCatalogSnapshot().version === 0) {
494
- updateCatalogStore(storedEnums || {}, storedRuntimeIds || [], storedThinking || {}, storedDeprecated || {});
495
- }
503
+ var PRIVATE_SNAPSHOT_KEY = "pi-provider-antigravity";
504
+ async function refreshCatalog(context, store) {
505
+ const persisted = fromPersistedSnapshot(context.stored?.[PRIVATE_SNAPSHOT_KEY]);
506
+ if (persisted) store.restore(persisted);
507
+ const storedModels = () => [...context.stored?.models ?? []];
496
508
  if (!context.allowNetwork) {
497
- return context.stored?.models || [];
509
+ return storedModels();
498
510
  }
499
511
  try {
500
- const apiKey = context.credential?.access;
512
+ const credential = context.credential;
513
+ const apiKey = credential?.type === "oauth" ? credential.access : void 0;
501
514
  if (!apiKey) {
502
- return context.stored?.models || [];
515
+ return storedModels();
503
516
  }
504
517
  const { token, projectId } = parseStoredCredentials(apiKey);
505
518
  const catalog = await fetchAvailableModelsCatalog(token, projectId, context.signal);
506
- ingestCatalog(catalog);
519
+ store.record(catalog);
507
520
  const dynamicModels = buildDynamicPublicModels(catalog);
508
521
  if (context.publish) {
509
- const snap = getCatalogSnapshot();
510
522
  await context.publish({
511
523
  persist: {
512
524
  models: dynamicModels,
513
525
  checkedAt: Date.now(),
514
- "pi-provider-antigravity": {
515
- modelEnums: { ...snap.enums },
516
- runtimeIds: [...snap.runtimeIds],
517
- thinking: { ...snap.thinking ?? {} },
518
- deprecated: { ...snap.deprecated ?? {} }
519
- }
526
+ [PRIVATE_SNAPSHOT_KEY]: toPersistedSnapshot(store.generation().snapshot)
520
527
  }
521
528
  });
522
529
  }
523
530
  return dynamicModels;
524
531
  } catch {
525
- return context.stored?.models || [];
532
+ return storedModels();
526
533
  }
527
534
  }
528
- async function refreshCatalogGeneration(doRefresh) {
529
- const before = getCatalogSnapshot().version;
535
+ async function refreshCatalogGeneration(store, doRefresh) {
536
+ const versionBefore = store.generation().version;
530
537
  await doRefresh();
531
- const catalog = getStoredCatalog();
532
- if (!catalog) return { status: "failed" };
533
- if (getCatalogSnapshot().version === before) return { status: "stale", catalog };
534
- return { status: "fresh", catalog };
538
+ const { items, version } = store.generation();
539
+ if (!items) return { status: "failed" };
540
+ return { status: version === versionBefore ? "stale" : "fresh", catalog: items };
535
541
  }
536
542
  function parseAvailableModels(data) {
537
543
  const models = [];
@@ -795,6 +801,46 @@ function validPairs(raw) {
795
801
  }
796
802
  return Object.keys(pairs).length > 0 ? pairs : void 0;
797
803
  }
804
+ var FOOTER_MODES = Object.freeze({
805
+ off: {
806
+ key: "off",
807
+ label: "Off",
808
+ note: "",
809
+ render: () => ({})
810
+ },
811
+ smart: {
812
+ key: "smart",
813
+ label: "Smart",
814
+ note: "Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage",
815
+ render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO) => {
816
+ if (!summary) return {};
817
+ const plain = buildQuotaFooter(summary, modelId, ratio);
818
+ return { plain, colored: colorizeQuotaFooter(plain) };
819
+ }
820
+ },
821
+ all: {
822
+ key: "all",
823
+ label: "All",
824
+ note: "Lists every window of the pool backing the current model (5h or weekly)",
825
+ render: (summary, modelId) => {
826
+ if (!summary) return {};
827
+ const plain = buildQuotaFooterBoth(summary, modelId);
828
+ return { plain, colored: colorizeQuotaFooterBoth(plain) };
829
+ }
830
+ }
831
+ });
832
+ var FOOTER_MODE_OPTIONS = Object.freeze(
833
+ Object.keys(FOOTER_MODES)
834
+ );
835
+ var FOOTER_MODE_NOTES = Object.freeze(
836
+ Object.fromEntries(
837
+ Object.entries(FOOTER_MODES).filter(([, def]) => def.note.length > 0).map(([key, def]) => [key, def.note])
838
+ )
839
+ );
840
+ function normalizeFooterMode(value) {
841
+ const v = typeof value === "string" ? value.trim().toLowerCase() : "";
842
+ return v in FOOTER_MODES ? v : void 0;
843
+ }
798
844
  function isAntigravityModel(model) {
799
845
  return model?.provider === PROVIDER_ID;
800
846
  }
@@ -804,11 +850,8 @@ function paintQuotaStatus(coord, ctx) {
804
850
  ctx.ui.setStatus(QUOTA_STATUS_KEY, void 0);
805
851
  return;
806
852
  }
807
- const plain = coord.footerFor(ctx.model?.id, mode);
808
- ctx.ui.setStatus(
809
- QUOTA_STATUS_KEY,
810
- mode === "all" ? colorizeQuotaFooterBoth(plain) : colorizeQuotaFooter(plain)
811
- );
853
+ const { colored } = coord.renderFooter(ctx.model?.id, mode);
854
+ ctx.ui.setStatus(QUOTA_STATUS_KEY, colored);
812
855
  }
813
856
  var QuotaStatusCoordinator = class {
814
857
  summary;
@@ -830,8 +873,10 @@ var QuotaStatusCoordinator = class {
830
873
  return this.store.loadMode();
831
874
  }
832
875
  footerFor(modelId, mode = "smart") {
833
- if (!this.summary || mode === "off") return void 0;
834
- return mode === "all" ? buildQuotaFooterBoth(this.summary, modelId) : buildQuotaFooter(this.summary, modelId, this.weeklyTo5hRatio);
876
+ return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio).plain;
877
+ }
878
+ renderFooter(modelId, mode = "smart") {
879
+ return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio) ?? {};
835
880
  }
836
881
  get isFresh() {
837
882
  return !!this.summary && Date.now() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;
@@ -947,10 +992,6 @@ function saveProviderConfig(file, data) {
947
992
  return false;
948
993
  }
949
994
  }
950
- function normalizeFooterMode(value) {
951
- const v = typeof value === "string" ? value.trim().toLowerCase() : "";
952
- return v === "off" || v === "smart" || v === "all" ? v : void 0;
953
- }
954
995
  function resolveFooterMode(config) {
955
996
  return normalizeFooterMode(config?.settings?.quotaFooter) ?? "off";
956
997
  }
@@ -989,12 +1030,9 @@ var SETTINGS_FIELDS = Object.freeze([
989
1030
  key: "quotaFooter",
990
1031
  label: "Quota footer",
991
1032
  description: "Show remaining quota in the footer",
992
- options: ["off", "smart", "all"],
1033
+ options: FOOTER_MODE_OPTIONS,
993
1034
  defaultValue: "off",
994
- optionNotes: {
995
- smart: "Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage",
996
- all: "Lists every window of the pool backing the current model (5h or weekly)"
997
- },
1035
+ optionNotes: FOOTER_MODE_NOTES,
998
1036
  onChange: async (ctx, quotaStatus) => {
999
1037
  if (!quotaStatus) return;
1000
1038
  if (quotaStatus.mode() !== "off") await quotaStatus.refresh(ctx, { ignoreMode: true });
@@ -1020,10 +1058,11 @@ async function applySettingValue(field, value, file, ctx, quotaStatus) {
1020
1058
  return true;
1021
1059
  }
1022
1060
  function previewQuotaFooterText(coord, modelId, mode) {
1023
- if (!coord || mode === "off") return void 0;
1024
- const plain = coord.footerFor(modelId, mode === "all" ? "all" : "smart");
1025
- if (!plain) return void 0;
1026
- const colored = mode === "all" ? colorizeQuotaFooterBoth(plain) : colorizeQuotaFooter(plain);
1061
+ if (!coord) return void 0;
1062
+ const normalized = normalizeFooterMode(mode);
1063
+ if (!normalized || normalized === "off") return void 0;
1064
+ const { colored } = coord.renderFooter(modelId, normalized);
1065
+ if (!colored) return void 0;
1027
1066
  return ANSI_FG_RESET + colored;
1028
1067
  }
1029
1068
  function buildSettingsItems(config, fields = SETTINGS_FIELDS) {
@@ -1152,6 +1191,7 @@ function createSseFeed() {
1152
1191
  input: 0,
1153
1192
  output: 0,
1154
1193
  cacheRead: 0,
1194
+ reasoning: 0,
1155
1195
  total: 0
1156
1196
  };
1157
1197
  let stopReason = "stop";
@@ -1166,18 +1206,23 @@ function createSseFeed() {
1166
1206
  });
1167
1207
  const attachLoneSignature = () => {
1168
1208
  if (!lastThoughtSignature) return;
1209
+ const attached = content.some(
1210
+ (b) => b.type === "thinking" ? Boolean(b.thinkingSignature) : b.type === "toolCall" ? Boolean(b.thoughtSignature) : b.type === "text" ? Boolean(b.textSignature) : false
1211
+ );
1212
+ if (attached) return;
1169
1213
  const thinking = content.find((b) => b.type === "thinking");
1170
- if (thinking && !thinking.thinkingSignature) {
1171
- thinking.thinkingSignature = lastThoughtSignature;
1172
- } else if (!thinking) {
1173
- for (let i = content.length - 1; i >= 0; i--) {
1174
- const block = content[i];
1175
- if (block?.type === "text" && !block.textSignature) {
1176
- block.textSignature = lastThoughtSignature;
1177
- break;
1178
- }
1214
+ if (thinking) {
1215
+ if (!thinking.thinkingSignature) thinking.thinkingSignature = lastThoughtSignature;
1216
+ return;
1217
+ }
1218
+ for (let i = content.length - 1; i >= 0; i--) {
1219
+ const block = content[i];
1220
+ if (block?.type === "text" && !block.textSignature) {
1221
+ block.textSignature = lastThoughtSignature;
1222
+ return;
1179
1223
  }
1180
1224
  }
1225
+ content.push({ type: "text", text: "", textSignature: lastThoughtSignature });
1181
1226
  };
1182
1227
  const closeOpenBlock = (events) => {
1183
1228
  if (openType === null) return;
@@ -1211,6 +1256,7 @@ function createSseFeed() {
1211
1256
  const candidates = typeof um.candidatesTokenCount === "number" ? um.candidatesTokenCount : 0;
1212
1257
  const thoughts = typeof um.thoughtsTokenCount === "number" ? um.thoughtsTokenCount : 0;
1213
1258
  usage.output = candidates + thoughts;
1259
+ usage.reasoning = thoughts;
1214
1260
  if (typeof um.totalTokenCount === "number") usage.total = um.totalTokenCount;
1215
1261
  }
1216
1262
  const candidate = response.candidates?.[0];
@@ -1226,6 +1272,8 @@ function createSseFeed() {
1226
1272
  lastThoughtSignature = part.thoughtSignature;
1227
1273
  }
1228
1274
  if (part.thought) {
1275
+ const delta = part.text || "";
1276
+ if (delta === "" && openType !== "thinking") continue;
1229
1277
  if (openType !== "thinking") {
1230
1278
  closeOpenBlock(events);
1231
1279
  content.push({ type: "thinking", thinking: "" });
@@ -1234,7 +1282,6 @@ function createSseFeed() {
1234
1282
  }
1235
1283
  const block = content[content.length - 1];
1236
1284
  if (block?.type !== "thinking") continue;
1237
- const delta = part.text || "";
1238
1285
  block.thinking += delta;
1239
1286
  if (part.thoughtSignature) {
1240
1287
  block.thinkingSignature = part.thoughtSignature;
@@ -1264,6 +1311,7 @@ function createSseFeed() {
1264
1311
  });
1265
1312
  events.push({ type: "toolcall_end", contentIndex, toolCall: block });
1266
1313
  } else if (part.text !== void 0) {
1314
+ if (part.text === "" && openType !== "text") continue;
1267
1315
  if (openType !== "text") {
1268
1316
  closeOpenBlock(events);
1269
1317
  content.push({ type: "text", text: "" });
@@ -1411,6 +1459,7 @@ function translateTurnTrace(context, runtimeModelId) {
1411
1459
  const contents = [];
1412
1460
  const messages = context.messages || [];
1413
1461
  const isGeminiRequest = classifyModelFamily(runtimeModelId) === "gemini";
1462
+ const isGptRequest = classifyModelFamily(runtimeModelId) === "gpt";
1414
1463
  for (const msg of messages) {
1415
1464
  if (msg.role === "user") {
1416
1465
  const parts = [];
@@ -1464,6 +1513,7 @@ function translateTurnTrace(context, runtimeModelId) {
1464
1513
  } else if (item.type === "thinking") {
1465
1514
  const candidateSig = item.thinkingSignature;
1466
1515
  const isForeignReasoning = !isSameProviderAndModel || typeof candidateSig === "string" && candidateSig.trim().startsWith("{");
1516
+ if (isGptRequest && !isForeignReasoning) continue;
1467
1517
  if (!isForeignReasoning) {
1468
1518
  const part = {
1469
1519
  thought: true,
@@ -1601,7 +1651,9 @@ function buildAntigravityRequestBody(params) {
1601
1651
  const sessionId = deriveSessionId(context, params.sessionId);
1602
1652
  const trajectoryId = params.trajectoryId && typeof params.trajectoryId === "string" && params.trajectoryId.trim().length > 0 ? params.trajectoryId.trim() : resolveSessionTrajectory(sessionId);
1603
1653
  const contents = translateTurnTrace(context, runtimeModelId);
1604
- const requestIndex = context.messages.filter((m) => m.role === "assistant").length;
1654
+ const requestIndex = contents.filter(
1655
+ (c) => c.role === "model" && !c.parts?.some((p) => p.functionResponse)
1656
+ ).length;
1605
1657
  const requestId = `agent/${sessionId}/${Date.now()}/${trajectoryId}/${contents.length}`;
1606
1658
  const modelEnum = plan.modelEnum;
1607
1659
  const labels = {
@@ -1655,7 +1707,7 @@ function buildAntigravityRequestBody(params) {
1655
1707
  }
1656
1708
 
1657
1709
  // src/stream.ts
1658
- function streamAntigravity(model, context, options) {
1710
+ function streamAntigravity(model, context, options, store) {
1659
1711
  const stream = createAssistantMessageEventStream();
1660
1712
  (async () => {
1661
1713
  const output = {
@@ -1684,7 +1736,7 @@ function streamAntigravity(model, context, options) {
1684
1736
  const { token, projectId } = parseStoredCredentials(rawApiKey);
1685
1737
  const effort = options?.reasoning;
1686
1738
  const trajectoryId = options && "trajectoryId" in options && typeof options.trajectoryId === "string" ? options.trajectoryId : void 0;
1687
- const snapshot = getCatalogSnapshot();
1739
+ const snapshot = store.generation().snapshot;
1688
1740
  const plan = resolveModelPlan(model.id, effort, snapshot);
1689
1741
  const requestBody = buildAntigravityRequestBody({
1690
1742
  projectId,
@@ -1716,6 +1768,7 @@ function streamAntigravity(model, context, options) {
1716
1768
  output.usage.input = fed.usage.input;
1717
1769
  output.usage.cacheRead = fed.usage.cacheRead;
1718
1770
  output.usage.output = fed.usage.output;
1771
+ output.usage.reasoning = fed.usage.reasoning;
1719
1772
  output.usage.totalTokens = fed.usage.total;
1720
1773
  if (model.cost) {
1721
1774
  const c = model.cost;
@@ -1759,13 +1812,75 @@ function streamAntigravity(model, context, options) {
1759
1812
  }
1760
1813
 
1761
1814
  // src/commands.ts
1762
- var USAGE_TEXT = "Usage: /antigravity <command>\n\nCommands:\n usage Show 5h and weekly quota pool limits\n models List recommended models with context window and remaining quota (alias: model)\n refresh Force refresh model catalog\n settings Pick provider settings: Enter cycles values (alias: setting)\n login Run /login antigravity";
1815
+ var SUBCOMMANDS = Object.freeze([
1816
+ {
1817
+ name: "usage",
1818
+ description: "Show 5h and weekly quota pool limits",
1819
+ run: async ({ ctx, quotaStatus }) => runUsageSubcommand(ctx, quotaStatus)
1820
+ },
1821
+ {
1822
+ name: "models",
1823
+ description: "List recommended models with context window and remaining quota",
1824
+ aliases: Object.freeze(["model"]),
1825
+ run: async ({ ctx, store }) => runModelsSubcommand(ctx, store)
1826
+ },
1827
+ {
1828
+ name: "refresh",
1829
+ description: "Force refresh model catalog",
1830
+ run: async ({ ctx, store }) => runRefreshSubcommand(ctx, store)
1831
+ },
1832
+ {
1833
+ name: "settings",
1834
+ description: "Pick provider settings: Enter cycles values",
1835
+ aliases: Object.freeze(["setting"]),
1836
+ run: async ({ ctx, quotaStatus }) => openSettings(ctx, quotaStatus)
1837
+ },
1838
+ {
1839
+ name: "login",
1840
+ description: "Run /login antigravity",
1841
+ run: ({ ctx }) => runLoginSubcommand(ctx)
1842
+ }
1843
+ ]);
1844
+ function buildUsageText(commands = SUBCOMMANDS) {
1845
+ const maxNameLen = Math.max(...commands.map((c) => c.name.length));
1846
+ const lines = commands.map((c) => {
1847
+ const aliasPart = c.aliases?.length ? ` (alias: ${c.aliases.join(", ")})` : "";
1848
+ return ` ${c.name.padEnd(maxNameLen)} ${c.description}${aliasPart}`;
1849
+ });
1850
+ return `Usage: /antigravity <command>
1851
+
1852
+ Commands:
1853
+ ${lines.join("\n")}`;
1854
+ }
1855
+ var USAGE_TEXT = buildUsageText();
1856
+ function completeSubcommands(prefix, commands = SUBCOMMANDS) {
1857
+ const trimmed = prefix.trim();
1858
+ if (trimmed === "") {
1859
+ return commands.map((s) => ({ value: s.name, label: s.name, description: s.description }));
1860
+ }
1861
+ if (/\s/.test(trimmed)) return null;
1862
+ const lower = trimmed.toLowerCase();
1863
+ return commands.filter((s) => s.name.startsWith(lower)).map((s) => ({
1864
+ value: s.name,
1865
+ label: s.name,
1866
+ description: s.description
1867
+ }));
1868
+ }
1869
+ function parseAntigravitySubcommand(args, commands = SUBCOMMANDS) {
1870
+ const sub = (args || "").trim().toLowerCase();
1871
+ for (const cmd of commands) {
1872
+ if (cmd.name === sub || cmd.aliases?.includes(sub)) {
1873
+ return cmd.name;
1874
+ }
1875
+ }
1876
+ return sub;
1877
+ }
1763
1878
  async function resolveToken(ctx) {
1764
1879
  const apiKey = await ctx.modelRegistry?.getApiKeyForProvider?.(PROVIDER_ID);
1765
1880
  if (!apiKey) return null;
1766
1881
  return parseStoredCredentials(apiKey);
1767
1882
  }
1768
- async function runModelsSubcommand(ctx) {
1883
+ async function runModelsSubcommand(ctx, store) {
1769
1884
  if (await resolveToken(ctx) === null) {
1770
1885
  emitOutput(ctx, "Not logged in. Run /login antigravity first.", "warning");
1771
1886
  return;
@@ -1773,6 +1888,7 @@ async function runModelsSubcommand(ctx) {
1773
1888
  try {
1774
1889
  if (ctx.hasUI) ctx.ui.notify("Fetching available models\u2026", "info");
1775
1890
  const { status, catalog } = await refreshCatalogGeneration(
1891
+ store,
1776
1892
  () => ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal })
1777
1893
  );
1778
1894
  if (status === "failed" || !catalog) {
@@ -1788,12 +1904,6 @@ async function runModelsSubcommand(ctx) {
1788
1904
  emitOutput(ctx, `Failed to fetch models: ${err.message}`, "error");
1789
1905
  }
1790
1906
  }
1791
- function parseAntigravitySubcommand(args) {
1792
- const sub = (args || "").trim().toLowerCase();
1793
- if (sub === "model") return "models";
1794
- if (sub === "setting") return "settings";
1795
- return sub;
1796
- }
1797
1907
  async function runUsageSubcommand(ctx, quotaStatus) {
1798
1908
  if (await resolveToken(ctx) === null) {
1799
1909
  emitOutput(ctx, "Not logged in. Run /login antigravity first.", "warning");
@@ -1816,46 +1926,38 @@ async function runUsageSubcommand(ctx, quotaStatus) {
1816
1926
  emitOutput(ctx, `Failed to fetch usage: ${err.message}`, "error");
1817
1927
  }
1818
1928
  }
1819
- async function runAntigravitySubcommand(args, ctx, quotaStatus) {
1820
- const parts = (args || "").trim().split(/\s+/).filter(Boolean);
1821
- const sub = parseAntigravitySubcommand(parts[0] || "");
1822
- if (sub === "usage") {
1823
- await runUsageSubcommand(ctx, quotaStatus);
1824
- return;
1825
- }
1826
- if (sub === "models") {
1827
- await runModelsSubcommand(ctx);
1828
- return;
1829
- }
1830
- if (sub === "settings") {
1831
- await openSettings(ctx, quotaStatus);
1832
- return;
1833
- }
1834
- if (sub === "refresh") {
1835
- try {
1836
- if (ctx.hasUI) ctx.ui.notify("Refreshing models\u2026", "info");
1837
- const { status } = await refreshCatalogGeneration(
1838
- () => ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal })
1839
- );
1840
- if (status === "fresh") {
1841
- emitOutput(ctx, "Antigravity models refreshed successfully.");
1842
- } else if (status === "stale") {
1843
- emitOutput(ctx, "Failed to refresh models, keeping last known list.", "warning");
1844
- } else {
1845
- emitOutput(ctx, "Failed to refresh models: no new generation received.", "error");
1846
- }
1847
- } catch (err) {
1848
- emitOutput(ctx, `Failed to refresh: ${err.message}`, "error");
1849
- }
1850
- return;
1851
- }
1852
- if (sub === "login") {
1853
- if (ctx.hasUI) {
1854
- ctx.ui.setEditorText("/login antigravity");
1855
- ctx.ui.notify("Press Enter to log in to Antigravity.", "info");
1929
+ async function runRefreshSubcommand(ctx, store) {
1930
+ try {
1931
+ if (ctx.hasUI) ctx.ui.notify("Refreshing models\u2026", "info");
1932
+ const { status } = await refreshCatalogGeneration(
1933
+ store,
1934
+ () => ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal })
1935
+ );
1936
+ if (status === "fresh") {
1937
+ emitOutput(ctx, "Antigravity models refreshed successfully.");
1938
+ } else if (status === "stale") {
1939
+ emitOutput(ctx, "Failed to refresh models, keeping last known list.", "warning");
1856
1940
  } else {
1857
- emitOutput(ctx, "Please run /login antigravity to authenticate.");
1941
+ emitOutput(ctx, "Failed to refresh models: no new generation received.", "error");
1858
1942
  }
1943
+ } catch (err) {
1944
+ emitOutput(ctx, `Failed to refresh: ${err.message}`, "error");
1945
+ }
1946
+ }
1947
+ function runLoginSubcommand(ctx) {
1948
+ if (ctx.hasUI) {
1949
+ ctx.ui.setEditorText("/login antigravity");
1950
+ ctx.ui.notify("Press Enter to log in to Antigravity.", "info");
1951
+ } else {
1952
+ emitOutput(ctx, "Please run /login antigravity to authenticate.");
1953
+ }
1954
+ }
1955
+ async function runAntigravitySubcommand(args, ctx, quotaStatus, store) {
1956
+ const parts = (args || "").trim().split(/\s+/).filter(Boolean);
1957
+ const sub = parseAntigravitySubcommand(parts[0] || "");
1958
+ const cmd = SUBCOMMANDS.find((c) => c.name === sub);
1959
+ if (cmd) {
1960
+ await cmd.run({ ctx, quotaStatus, store });
1859
1961
  return;
1860
1962
  }
1861
1963
  emitOutput(ctx, USAGE_TEXT);
@@ -1863,15 +1965,9 @@ async function runAntigravitySubcommand(args, ctx, quotaStatus) {
1863
1965
 
1864
1966
  // src/index.ts
1865
1967
  var PROVIDER_NAME = "Antigravity";
1866
- var SUBCOMMANDS = [
1867
- { name: "usage", description: "Show 5h and weekly quota pool limits" },
1868
- { name: "models", description: "List recommended models with context window and remaining quota" },
1869
- { name: "refresh", description: "Force refresh model catalog" },
1870
- { name: "settings", description: "Pick provider settings: Enter cycles values" },
1871
- { name: "login", description: "Run /login antigravity" }
1872
- ];
1873
1968
  function index_default(pi) {
1874
1969
  const quotaStatus = new QuotaStatusCoordinator(fileQuotaStatusStore());
1970
+ const catalog = createCatalogStore();
1875
1971
  pi.registerProvider(PROVIDER_ID, {
1876
1972
  name: PROVIDER_NAME,
1877
1973
  baseUrl: DEFAULT_ENDPOINT,
@@ -1884,26 +1980,14 @@ function index_default(pi) {
1884
1980
  refreshToken: refreshAntigravityToken,
1885
1981
  getApiKey
1886
1982
  },
1887
- streamSimple: streamAntigravity,
1888
- refreshModels: async (context) => refreshCatalog(context)
1983
+ streamSimple: (model, context, options) => streamAntigravity(model, context, options, catalog),
1984
+ refreshModels: async (context) => refreshCatalog(context, catalog)
1889
1985
  });
1890
1986
  pi.registerCommand("antigravity", {
1891
1987
  description: "Antigravity quota, models and settings",
1892
- getArgumentCompletions: (prefix) => {
1893
- const trimmed = prefix.trim();
1894
- if (trimmed === "") {
1895
- return SUBCOMMANDS.map((s) => ({ value: s.name, label: s.name, description: s.description }));
1896
- }
1897
- if (/\s/.test(trimmed)) return null;
1898
- const lower = trimmed.toLowerCase();
1899
- return SUBCOMMANDS.filter((s) => s.name.startsWith(lower)).map((s) => ({
1900
- value: s.name,
1901
- label: s.name,
1902
- description: s.description
1903
- }));
1904
- },
1988
+ getArgumentCompletions: (prefix) => completeSubcommands(prefix),
1905
1989
  handler: async (args, ctx) => {
1906
- await runAntigravitySubcommand(args, ctx, quotaStatus);
1990
+ await runAntigravitySubcommand(args, ctx, quotaStatus, catalog);
1907
1991
  }
1908
1992
  });
1909
1993
  pi.on("session_start", async (_event, ctx) => {