pi-provider-antigravity 0.15.0 → 0.16.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/README.md CHANGED
@@ -22,7 +22,7 @@ Use Google Antigravity models directly in [Pi](https://pi.dev).
22
22
 
23
23
  ## Requirements
24
24
 
25
- - Pi >= 0.86.0 (For Pi 0.80.0 ~ 0.85.x, see [Legacy Pi Support](#legacy-pi-support))
25
+ - **Pi >= 0.86.0** (Pi 0.86+ introduced breaking changes; older versions are no longer supported. Please upgrade Pi, or pin to `0.13.0` via `pi install npm:pi-provider-antigravity@0.13.0` if using Pi 0.80 ~ 0.85.)
26
26
  - A Google account with Antigravity access
27
27
 
28
28
  ## Installation
@@ -31,28 +31,6 @@ Use Google Antigravity models directly in [Pi](https://pi.dev).
31
31
  pi install npm:pi-provider-antigravity
32
32
  ```
33
33
 
34
- Or try it without installing:
35
-
36
- ```bash
37
- pi -e npm:pi-provider-antigravity
38
- ```
39
-
40
- ### Legacy Pi Support
41
-
42
- We strongly recommend upgrading to Pi >= 0.86.0 to take advantage of the latest SDK capabilities, streaming reliability improvements, and full feature parity.
43
-
44
- However, if you are unable to upgrade your Pi installation just yet (for environment or workflow reasons), you can pin to the compatible `0.13.0` release for Pi 0.80.0 ~ 0.85.x:
45
-
46
- ```bash
47
- pi install npm:pi-provider-antigravity@0.13.0
48
- ```
49
-
50
- Or try it without installing:
51
-
52
- ```bash
53
- pi -e npm:pi-provider-antigravity@0.13.0
54
- ```
55
-
56
34
  ## Quick Start
57
35
 
58
36
  1. Authenticate:
package/dist/index.js CHANGED
@@ -1,12 +1,9 @@
1
- // src/index.ts
2
- import { Type } from "@earendil-works/pi-ai";
3
-
4
1
  // src/auth.ts
5
2
  import { createHash, randomBytes } from "node:crypto";
6
3
 
7
4
  // src/protocol.ts
8
5
  var DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
9
- var DEFAULT_USER_AGENT = "antigravity/cli/1.2.5 (aidev_client; os_type=linux; arch=amd64; cl=982839923; auth_method=consumer)";
6
+ var DEFAULT_USER_AGENT = "antigravity/cli/1.2.7 (aidev_client; os_type=linux; arch=amd64; cl=984112147; auth_method=consumer)";
10
7
  var PROVIDER_ID = "antigravity";
11
8
  var AntigravityHttpError = class extends Error {
12
9
  status;
@@ -787,33 +784,6 @@ function saveSubsystemState(subsystem, state, file = defaultConfigFile()) {
787
784
  config.states = states;
788
785
  });
789
786
  }
790
- var SettingsRegistry = class {
791
- fields = [];
792
- register(field) {
793
- const existingIndex = this.fields.findIndex((f) => f.key === field.key);
794
- if (existingIndex >= 0) {
795
- this.fields[existingIndex] = field;
796
- } else {
797
- this.fields.push(field);
798
- }
799
- }
800
- unregister(key) {
801
- this.fields = this.fields.filter((f) => f.key !== key);
802
- }
803
- getFields() {
804
- return [...this.fields];
805
- }
806
- clear() {
807
- this.fields = [];
808
- }
809
- };
810
- var defaultSettingsRegistry = new SettingsRegistry();
811
- function registerSettingField(field) {
812
- defaultSettingsRegistry.register(field);
813
- }
814
- function getRegisteredSettingFields() {
815
- return defaultSettingsRegistry.getFields();
816
- }
817
787
  function fieldDisplayValue(config, field) {
818
788
  const raw = config?.settings?.[field.key];
819
789
  if (typeof raw === "string" && raw.trim()) return raw.trim();
@@ -836,7 +806,7 @@ async function applySettingValue(field, value, file, ctx) {
836
806
  function writeFailedMessage(file) {
837
807
  return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;
838
808
  }
839
- function buildSettingsItems(config, fields = getRegisteredSettingFields()) {
809
+ function buildSettingsItems(config, fields) {
840
810
  return fields.map((f) => ({
841
811
  id: f.key,
842
812
  label: f.label,
@@ -859,15 +829,7 @@ async function settingsListTheme(theme) {
859
829
  };
860
830
  }
861
831
  }
862
- function resolveFields(registryOrFields) {
863
- if (!registryOrFields) return getRegisteredSettingFields();
864
- if ("getFields" in registryOrFields && typeof registryOrFields.getFields === "function") {
865
- return registryOrFields.getFields();
866
- }
867
- return registryOrFields;
868
- }
869
- async function openSettings(ctx, registryOrFields) {
870
- const fields = resolveFields(registryOrFields);
832
+ async function openSettings(ctx, fields) {
871
833
  const file = defaultConfigFile();
872
834
  if (ctx.mode === "tui" && ctx.hasUI) {
873
835
  await openSettingsDialog(ctx, fields, file);
@@ -1007,11 +969,11 @@ function sortQuotaBuckets(buckets) {
1007
969
  (a, b) => QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)]
1008
970
  );
1009
971
  }
1010
- function formatResetRemaining(resetTime) {
972
+ function formatResetRemaining(resetTime, nowFn = Date.now) {
1011
973
  if (!resetTime) return null;
1012
974
  const ts = Date.parse(resetTime);
1013
975
  if (!Number.isFinite(ts)) return null;
1014
- const delta = ts - Date.now();
976
+ const delta = ts - nowFn();
1015
977
  if (delta <= 0) return "ready";
1016
978
  const totalMin = Math.ceil(delta / 6e4);
1017
979
  const days = Math.floor(totalMin / (60 * 24));
@@ -1046,7 +1008,7 @@ function parseQuotaSummary(data) {
1046
1008
  description: data.description
1047
1009
  };
1048
1010
  }
1049
- function formatQuotaSummary(summary) {
1011
+ function formatQuotaSummary(summary, nowFn = Date.now) {
1050
1012
  const lines = [];
1051
1013
  for (const group of summary.groups) {
1052
1014
  if (lines.length > 0) lines.push("");
@@ -1054,7 +1016,7 @@ function formatQuotaSummary(summary) {
1054
1016
  for (const b of sortQuotaBuckets(group.buckets)) {
1055
1017
  const pct = Math.round(b.remainingFraction * 100);
1056
1018
  const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);
1057
- const reset = formatResetRemaining(b.resetTime);
1019
+ const reset = formatResetRemaining(b.resetTime, nowFn);
1058
1020
  const suffix = reset && pct < 100 ? ` (${reset})` : "";
1059
1021
  lines.push(
1060
1022
  ` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`
@@ -1127,22 +1089,22 @@ function selectUrgentBucket(group, weeklyTo5hRatio) {
1127
1089
  }
1128
1090
  return worst;
1129
1091
  }
1130
- function buildQuotaFooter(summary, modelId, weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
1092
+ function buildQuotaFooter(summary, modelId, weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO, nowFn = Date.now) {
1131
1093
  const group = selectQuotaGroup(summary.groups, modelId);
1132
1094
  if (!group) return void 0;
1133
1095
  const urgent = selectUrgentBucket(group, weeklyTo5hRatio);
1134
- return urgent ? formatQuotaWindowPart(urgent) : void 0;
1096
+ return urgent ? formatQuotaWindowPart(urgent, nowFn) : void 0;
1135
1097
  }
1136
- function buildQuotaFooterBoth(summary, modelId) {
1098
+ function buildQuotaFooterBoth(summary, modelId, nowFn = Date.now) {
1137
1099
  const group = selectQuotaGroup(summary.groups, modelId);
1138
1100
  if (!group || group.buckets.length === 0) return void 0;
1139
- return sortQuotaBuckets(group.buckets).map(formatQuotaWindowPart).join(" \xB7 ");
1101
+ return sortQuotaBuckets(group.buckets).map((b) => formatQuotaWindowPart(b, nowFn)).join(" \xB7 ");
1140
1102
  }
1141
- function formatQuotaWindowPart(bucket) {
1103
+ function formatQuotaWindowPart(bucket, nowFn = Date.now) {
1142
1104
  const kind = classifyQuotaWindow(bucket);
1143
1105
  const prefix = kind === "5h" ? "5h" : kind === "weekly" ? "Wk" : bucket.displayName || bucket.bucketId;
1144
1106
  const pct = Math.round(bucket.remainingFraction * 100);
1145
- const short = formatResetRemaining(bucket.resetTime)?.replace(/^in /, "");
1107
+ const short = formatResetRemaining(bucket.resetTime, nowFn)?.replace(/^in /, "");
1146
1108
  if (short && pct < 100) return `${prefix} ${pct}% (${short})`;
1147
1109
  return `${prefix} ${pct}%`;
1148
1110
  }
@@ -1202,25 +1164,22 @@ function createQuotaFooterField(quotaStatus) {
1202
1164
  options: FOOTER_MODE_OPTIONS,
1203
1165
  defaultValue: "off",
1204
1166
  optionNotes: FOOTER_MODE_NOTES,
1205
- renderPreview: (mode, ctx, theme) => {
1167
+ renderPreview: quotaStatus ? (mode, ctx, theme) => {
1206
1168
  if (mode === "off") return "hidden";
1207
1169
  const activeTheme = theme || ctx.ui?.theme;
1208
1170
  return previewQuotaFooterText(quotaStatus, ctx.model?.id, mode, activeTheme);
1209
- },
1210
- prepare: async (ctx) => {
1171
+ } : void 0,
1172
+ prepare: quotaStatus ? async (ctx) => {
1211
1173
  await quotaStatus.ensurePreview(ctx);
1212
- },
1213
- onChange: async (ctx, _value) => {
1174
+ } : void 0,
1175
+ onChange: quotaStatus ? async (ctx, _value) => {
1214
1176
  if (quotaStatus.mode() !== "off") {
1215
1177
  await quotaStatus.refresh(ctx, { ignoreMode: true });
1216
1178
  }
1217
1179
  quotaStatus.paint(ctx);
1218
- }
1180
+ } : void 0
1219
1181
  };
1220
1182
  }
1221
- function registerQuotaSettings(quotaStatus) {
1222
- registerSettingField(createQuotaFooterField(quotaStatus));
1223
- }
1224
1183
  function isFiniteNumber(value) {
1225
1184
  return typeof value === "number" && Number.isFinite(value);
1226
1185
  }
@@ -1251,18 +1210,18 @@ var FOOTER_MODES = Object.freeze({
1251
1210
  smart: {
1252
1211
  key: "smart",
1253
1212
  note: FOOTER_MODE_NOTES.smart,
1254
- render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO, style) => {
1213
+ render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO, style, nowFn = Date.now) => {
1255
1214
  if (!summary) return {};
1256
- const plain = buildQuotaFooter(summary, modelId, ratio);
1215
+ const plain = buildQuotaFooter(summary, modelId, ratio, nowFn);
1257
1216
  return { plain, colored: colorizeQuotaFooter(plain, style) };
1258
1217
  }
1259
1218
  },
1260
1219
  all: {
1261
1220
  key: "all",
1262
1221
  note: FOOTER_MODE_NOTES.all,
1263
- render: (summary, modelId, _ratio, style) => {
1222
+ render: (summary, modelId, _ratio, style, nowFn = Date.now) => {
1264
1223
  if (!summary) return {};
1265
- const plain = buildQuotaFooterBoth(summary, modelId);
1224
+ const plain = buildQuotaFooterBoth(summary, modelId, nowFn);
1266
1225
  return { plain, colored: colorizeQuotaFooterBoth(plain, style) };
1267
1226
  }
1268
1227
  }
@@ -1277,11 +1236,19 @@ var QuotaStatusCoordinator = class {
1277
1236
  weeklyTo5hRatio;
1278
1237
  lastPairs;
1279
1238
  store;
1280
- constructor(storeOrFile) {
1281
- if (typeof storeOrFile === "string" || !storeOrFile) {
1282
- this.store = fileQuotaStatusStore(storeOrFile || defaultConfigFile());
1239
+ fetchSummaryOverride;
1240
+ nowFn;
1241
+ constructor(depsOrStoreOrFile) {
1242
+ if (typeof depsOrStoreOrFile === "string" || !depsOrStoreOrFile) {
1243
+ this.store = fileQuotaStatusStore(depsOrStoreOrFile || defaultConfigFile());
1244
+ this.nowFn = Date.now;
1245
+ } else if ("loadMode" in depsOrStoreOrFile) {
1246
+ this.store = depsOrStoreOrFile;
1247
+ this.nowFn = Date.now;
1283
1248
  } else {
1284
- this.store = storeOrFile;
1249
+ this.store = depsOrStoreOrFile.store ?? fileQuotaStatusStore(depsOrStoreOrFile.configFile || defaultConfigFile());
1250
+ this.fetchSummaryOverride = depsOrStoreOrFile.fetchQuotaSummary;
1251
+ this.nowFn = depsOrStoreOrFile.now ?? Date.now;
1285
1252
  }
1286
1253
  this.weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO;
1287
1254
  this.loadPersisted();
@@ -1294,13 +1261,13 @@ var QuotaStatusCoordinator = class {
1294
1261
  return this.store.loadMode();
1295
1262
  }
1296
1263
  footerFor(modelId, mode = "smart") {
1297
- return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio).plain;
1264
+ return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, void 0, this.nowFn).plain;
1298
1265
  }
1299
1266
  renderFooter(modelId, mode = "smart", style) {
1300
- return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, style) ?? {};
1267
+ return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, style, this.nowFn) ?? {};
1301
1268
  }
1302
1269
  get isFresh() {
1303
- return !!this.summary && Date.now() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;
1270
+ return !!this.summary && this.nowFn() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;
1304
1271
  }
1305
1272
  refresh(ctx, opts = {}) {
1306
1273
  if (!isLiveCtx(ctx)) return Promise.resolve(void 0);
@@ -1339,7 +1306,10 @@ var QuotaStatusCoordinator = class {
1339
1306
  throw new Error("Failed to fetch usage.");
1340
1307
  }
1341
1308
  this.paint(ctx);
1342
- return formatQuotaSummary(summary);
1309
+ return formatQuotaSummary(summary, this.nowFn);
1310
+ }
1311
+ createSettingsField() {
1312
+ return createQuotaFooterField(this);
1343
1313
  }
1344
1314
  // One fetch for preview purposes even when the footer slot is off or the
1345
1315
  // current model belongs to another provider: opening quota settings is an
@@ -1360,22 +1330,29 @@ var QuotaStatusCoordinator = class {
1360
1330
  try {
1361
1331
  const creds = await resolveCredentials(ctx);
1362
1332
  if (!creds) return void 0;
1363
- const rawSummary = await postAntigravityJson({
1364
- auth: creds,
1365
- path: "v1internal:retrieveUserQuotaSummary",
1366
- body: { project: creds.projectId },
1367
- signal
1368
- });
1369
- const summary = parseQuotaSummary(rawSummary);
1333
+ let rawSummary;
1334
+ if (this.fetchSummaryOverride) {
1335
+ rawSummary = await this.fetchSummaryOverride(creds.token, signal);
1336
+ } else {
1337
+ rawSummary = await postAntigravityJson({
1338
+ auth: creds,
1339
+ path: "v1internal:retrieveUserQuotaSummary",
1340
+ body: { project: creds.projectId },
1341
+ signal
1342
+ });
1343
+ }
1344
+ if (!rawSummary) return void 0;
1345
+ const summary = rawSummary.groups ? parseQuotaSummary(rawSummary) : rawSummary;
1370
1346
  this.ingest(summary);
1371
1347
  return summary;
1372
1348
  } catch {
1373
1349
  return void 0;
1374
1350
  }
1375
1351
  }
1376
- // Accepts an externally fetched summary into the shared cache: same
1352
+ // Accepts an externally fetched summary or raw JSON into the shared cache: same
1377
1353
  // calibration, persistence, and freshness as fetch().
1378
- ingest(summary) {
1354
+ ingest(summaryOrRaw) {
1355
+ const summary = summaryOrRaw && summaryOrRaw.groups && typeof summaryOrRaw.groups[0]?.buckets?.[0]?.remainingFraction === "number" ? parseQuotaSummary(summaryOrRaw) : summaryOrRaw;
1379
1356
  const calibrated = calibrateWeeklyTo5hRatio(
1380
1357
  this.lastPairs,
1381
1358
  extractWindowFractionPairs(summary),
@@ -1384,7 +1361,7 @@ var QuotaStatusCoordinator = class {
1384
1361
  this.weeklyTo5hRatio = calibrated;
1385
1362
  this.lastPairs = extractWindowFractionPairs(summary);
1386
1363
  this.summary = summary;
1387
- this.fetchedAt = Date.now();
1364
+ this.fetchedAt = this.nowFn();
1388
1365
  this.saveState();
1389
1366
  }
1390
1367
  loadPersisted() {
@@ -1394,7 +1371,7 @@ var QuotaStatusCoordinator = class {
1394
1371
  this.weeklyTo5hRatio = ratio;
1395
1372
  }
1396
1373
  const at = entry?.updatedAt;
1397
- this.lastPairs = typeof at === "number" && Date.now() - at <= MAX_OBSERVATION_AGE_MS ? validPairs(entry?.previousObservation) : void 0;
1374
+ this.lastPairs = typeof at === "number" && this.nowFn() - at <= MAX_OBSERVATION_AGE_MS ? validPairs(entry?.previousObservation) : void 0;
1398
1375
  }
1399
1376
  // Persists calibration state through the injected store. Best-effort: a
1400
1377
  // stale ratio is still usable.
@@ -1403,12 +1380,15 @@ var QuotaStatusCoordinator = class {
1403
1380
  this.store.saveQuotaState({
1404
1381
  weeklyTo5hRatio: this.weeklyTo5hRatio,
1405
1382
  previousObservation: this.lastPairs ?? {},
1406
- updatedAt: Date.now()
1383
+ updatedAt: this.nowFn()
1407
1384
  });
1408
1385
  } catch {
1409
1386
  }
1410
1387
  }
1411
1388
  };
1389
+ function createQuotaStatus(deps) {
1390
+ return new QuotaStatusCoordinator(deps);
1391
+ }
1412
1392
 
1413
1393
  // src/stream.ts
1414
1394
  import {
@@ -1808,6 +1788,11 @@ function attachLoneSignature(content, lastThoughtSignature) {
1808
1788
  }
1809
1789
  content.push({ type: "text", text: "", textSignature: lastThoughtSignature });
1810
1790
  }
1791
+ function hasAnswerContent(content) {
1792
+ return content.some(
1793
+ (block) => block.type === "toolCall" ? true : block.type === "text" ? block.text.trim().length > 0 : false
1794
+ );
1795
+ }
1811
1796
  function closeOpenBlock(state, onEvent) {
1812
1797
  if (state.openType === null) return;
1813
1798
  const contentIndex = state.content.length - 1;
@@ -2025,13 +2010,14 @@ function streamAntigravity(model, context, options, catalog) {
2025
2010
  const effort = options?.reasoning;
2026
2011
  const trajectoryId = options && "trajectoryId" in options && typeof options.trajectoryId === "string" ? options.trajectoryId : void 0;
2027
2012
  const plan = catalog.resolvePlan(model.id, effort);
2013
+ const maxOutputTokens = options?.maxTokens ?? model.maxTokens;
2028
2014
  let requestBody = buildAntigravityRequestBody({
2029
2015
  projectId,
2030
2016
  plan,
2031
2017
  context,
2032
2018
  sessionId: options?.sessionId,
2033
2019
  trajectoryId,
2034
- maxOutputTokens: options?.maxTokens ?? model.maxTokens,
2020
+ maxOutputTokens,
2035
2021
  toolChoice: options?.toolChoice
2036
2022
  });
2037
2023
  if (options?.onPayload) {
@@ -2079,6 +2065,19 @@ function streamAntigravity(model, context, options, catalog) {
2079
2065
  output.rawStopReason ? `Provider stopped with: ${output.rawStopReason}` : output.errorMessage || "An unknown error occurred"
2080
2066
  );
2081
2067
  }
2068
+ if (output.stopReason === "stop" && !hasAnswerContent(output.content)) {
2069
+ const reasoningTokens = output.usage.reasoning ?? 0;
2070
+ const budgetThreshold = Math.max(1, Math.floor(maxOutputTokens * 0.9));
2071
+ const isDeliberatedToBudget = reasoningTokens >= budgetThreshold;
2072
+ if (isDeliberatedToBudget) {
2073
+ throw new Error(
2074
+ `Antigravity returned no answer: the turn ended after thinking only (reasoning ${reasoningTokens} of ${maxOutputTokens} max output tokens). Lower the thinking level or prompt again.`
2075
+ );
2076
+ }
2077
+ throw new Error(
2078
+ `Antigravity returned an empty response with no answer (reasoning ${reasoningTokens} of ${maxOutputTokens} max output tokens). Please retry your request.`
2079
+ );
2080
+ }
2082
2081
  const doneReason = output.stopReason === "toolUse" || output.stopReason === "length" ? output.stopReason : "stop";
2083
2082
  stream.push({
2084
2083
  type: "done",
@@ -2098,8 +2097,10 @@ function streamAntigravity(model, context, options, catalog) {
2098
2097
  }
2099
2098
 
2100
2099
  // src/search.ts
2101
- var SEARCH_SYSTEM_INSTRUCTION = "You are a search engine bot. You will be given a query from a user. Your task is to search the web for relevant information that will help the user. You MUST perform a web search. Do not respond or interact with the user, please respond as if they typed the query into a search bar.";
2100
+ import { Type } from "@earendil-works/pi-ai";
2101
+ var WEB_SEARCH_TOOL_NAME = "antigravity_websearch";
2102
2102
  var SEARCH_MODEL = "gemini-3.1-flash-lite";
2103
+ var SEARCH_SYSTEM_INSTRUCTION = "You are a search engine bot. You will be given a query from a user. Your task is to search the web for relevant information that will help the user. You MUST perform a web search. Do not respond or interact with the user, please respond as if they typed the query into a search bar.";
2103
2104
  function formatSearchResults(rawText, metadata, query, createdAt, completedAt) {
2104
2105
  const sources = (metadata?.groundingChunks || []).map((chunk, i) => ({
2105
2106
  index: i + 1,
@@ -2192,6 +2193,84 @@ async function performWebSearch(creds, options) {
2192
2193
  const metadata = candidate?.groundingMetadata;
2193
2194
  return formatSearchResults(rawText, metadata, effectiveQuery, createdAt, completedAt);
2194
2195
  }
2196
+ function notifyUser(ctx, text, type = "info") {
2197
+ if (ctx.hasUI) {
2198
+ ctx.ui.notify(text, type);
2199
+ } else {
2200
+ if (type === "error" || type === "warning") console.error(text);
2201
+ else console.log(text);
2202
+ }
2203
+ }
2204
+ function registerWebSearchTool(pi) {
2205
+ if (typeof pi.registerTool !== "function") return;
2206
+ pi.registerTool({
2207
+ name: WEB_SEARCH_TOOL_NAME,
2208
+ label: "Antigravity Web Search",
2209
+ description: "Performs a web search for a given query. Returns a summary of relevant information along with URL citations.",
2210
+ promptSnippet: "Search the web using Google Search Grounding with source citations",
2211
+ promptGuidelines: [
2212
+ "Use antigravity_websearch when you need real-time web search, latest documentation, or up-to-date facts with verified citations."
2213
+ ],
2214
+ parameters: Type.Object({
2215
+ query: Type.String({ description: "The search query string to look up on the web." }),
2216
+ domain: Type.Optional(
2217
+ Type.String({
2218
+ description: "Optional domain to recommend the search prioritize (e.g. 'github.com', 'developer.mozilla.org')."
2219
+ })
2220
+ )
2221
+ }),
2222
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
2223
+ const creds = await resolveCredentials(ctx);
2224
+ if (!creds) {
2225
+ return {
2226
+ content: [{ type: "text", text: `Error: ${NOT_LOGGED_IN}` }],
2227
+ details: { error: "not_logged_in" }
2228
+ };
2229
+ }
2230
+ try {
2231
+ const result = await performWebSearch(creds, {
2232
+ query: params.query,
2233
+ domain: params.domain,
2234
+ signal
2235
+ });
2236
+ return {
2237
+ content: [{ type: "text", text: result.formattedOutput }],
2238
+ details: {
2239
+ sources: result.sources,
2240
+ queries: result.queries
2241
+ }
2242
+ };
2243
+ } catch (err) {
2244
+ return {
2245
+ content: [{ type: "text", text: `Search failed: ${err.message}` }],
2246
+ details: { error: err.message }
2247
+ };
2248
+ }
2249
+ }
2250
+ });
2251
+ }
2252
+ async function executeWebSearchCommand(ctx, query) {
2253
+ const creds = await resolveCredentials(ctx);
2254
+ if (!creds) {
2255
+ notifyUser(ctx, NOT_LOGGED_IN, "warning");
2256
+ return;
2257
+ }
2258
+ const trimmedQuery = (query || "").trim();
2259
+ if (!trimmedQuery) {
2260
+ notifyUser(ctx, "Usage: /antigravity websearch <query>", "warning");
2261
+ return;
2262
+ }
2263
+ try {
2264
+ if (ctx.hasUI) ctx.ui.notify(`Searching: "${trimmedQuery}"\u2026`, "info");
2265
+ const result = await performWebSearch(creds, {
2266
+ query: trimmedQuery,
2267
+ signal: ctx.signal
2268
+ });
2269
+ notifyUser(ctx, result.formattedOutput);
2270
+ } catch (err) {
2271
+ notifyUser(ctx, `Search failed: ${err.message}`, "error");
2272
+ }
2273
+ }
2195
2274
 
2196
2275
  // src/commands.ts
2197
2276
  function emitOutput(ctx, text, type = "info") {
@@ -2224,14 +2303,14 @@ var SUBCOMMANDS = Object.freeze([
2224
2303
  description: "Configure provider settings",
2225
2304
  aliases: Object.freeze(["setting"]),
2226
2305
  run: async ({ ctx, quotaStatus }) => {
2227
- if (quotaStatus) registerQuotaSettings(quotaStatus);
2228
- await openSettings(ctx);
2306
+ const qs = quotaStatus ?? createQuotaStatus();
2307
+ await openSettings(ctx, [qs.createSettingsField()]);
2229
2308
  }
2230
2309
  },
2231
2310
  {
2232
2311
  name: "websearch",
2233
2312
  description: "Search the web using Google Search Grounding",
2234
- run: async ({ ctx, subArgs }) => runSearchSubcommand(ctx, subArgs)
2313
+ run: async ({ ctx, subArgs }) => executeWebSearchCommand(ctx, subArgs)
2235
2314
  },
2236
2315
  {
2237
2316
  name: "login",
@@ -2338,28 +2417,6 @@ function runLoginSubcommand(ctx) {
2338
2417
  emitOutput(ctx, "Please run /login antigravity to authenticate.");
2339
2418
  }
2340
2419
  }
2341
- async function runSearchSubcommand(ctx, query) {
2342
- const creds = await resolveCredentials(ctx);
2343
- if (!creds) {
2344
- emitOutput(ctx, NOT_LOGGED_IN, "warning");
2345
- return;
2346
- }
2347
- const trimmedQuery = (query || "").trim();
2348
- if (!trimmedQuery) {
2349
- emitOutput(ctx, "Usage: /antigravity websearch <query>", "warning");
2350
- return;
2351
- }
2352
- try {
2353
- if (ctx.hasUI) ctx.ui.notify(`Searching: "${trimmedQuery}"\u2026`, "info");
2354
- const result = await performWebSearch(creds, {
2355
- query: trimmedQuery,
2356
- signal: ctx.signal
2357
- });
2358
- emitOutput(ctx, result.formattedOutput);
2359
- } catch (err) {
2360
- emitOutput(ctx, `Search failed: ${err.message}`, "error");
2361
- }
2362
- }
2363
2420
  async function runAntigravitySubcommand(args, ctx, quotaStatus, catalog) {
2364
2421
  const raw = (args || "").trim();
2365
2422
  const parts = raw.split(/\s+/).filter(Boolean);
@@ -2376,8 +2433,7 @@ async function runAntigravitySubcommand(args, ctx, quotaStatus, catalog) {
2376
2433
  // src/index.ts
2377
2434
  var PROVIDER_NAME = "Antigravity";
2378
2435
  function index_default(pi) {
2379
- const quotaStatus = new QuotaStatusCoordinator();
2380
- registerQuotaSettings(quotaStatus);
2436
+ const quotaStatus = createQuotaStatus();
2381
2437
  const catalog = createModelCatalog();
2382
2438
  pi.registerProvider(PROVIDER_ID, {
2383
2439
  name: PROVIDER_NAME,
@@ -2402,53 +2458,7 @@ function index_default(pi) {
2402
2458
  await runAntigravitySubcommand(args, ctx, quotaStatus, catalog);
2403
2459
  }
2404
2460
  });
2405
- if (typeof pi.registerTool === "function") {
2406
- pi.registerTool({
2407
- name: "antigravity_websearch",
2408
- label: "Antigravity Web Search",
2409
- description: "Performs a web search for a given query. Returns a summary of relevant information along with URL citations.",
2410
- promptSnippet: "Search the web using Google Search Grounding with source citations",
2411
- promptGuidelines: [
2412
- "Use antigravity_websearch when you need real-time web search, latest documentation, or up-to-date facts with verified citations."
2413
- ],
2414
- parameters: Type.Object({
2415
- query: Type.String({ description: "The search query string to look up on the web." }),
2416
- domain: Type.Optional(
2417
- Type.String({
2418
- description: "Optional domain to recommend the search prioritize (e.g. 'github.com', 'developer.mozilla.org')."
2419
- })
2420
- )
2421
- }),
2422
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
2423
- const creds = await resolveCredentials(ctx);
2424
- if (!creds) {
2425
- return {
2426
- content: [{ type: "text", text: "Error: Not logged in. Run /login antigravity first." }],
2427
- details: { error: "not_logged_in" }
2428
- };
2429
- }
2430
- try {
2431
- const result = await performWebSearch(creds, {
2432
- query: params.query,
2433
- domain: params.domain,
2434
- signal
2435
- });
2436
- return {
2437
- content: [{ type: "text", text: result.formattedOutput }],
2438
- details: {
2439
- sources: result.sources,
2440
- queries: result.queries
2441
- }
2442
- };
2443
- } catch (err) {
2444
- return {
2445
- content: [{ type: "text", text: `Search failed: ${err.message}` }],
2446
- details: { error: err.message }
2447
- };
2448
- }
2449
- }
2450
- });
2451
- }
2461
+ registerWebSearchTool(pi);
2452
2462
  pi.on("session_start", async (_event, ctx) => {
2453
2463
  await quotaStatus.refreshAndPaint(ctx);
2454
2464
  });