pi-provider-antigravity 0.8.0 → 0.9.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 +5 -2
- package/dist/index.js +319 -108
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,14 +9,16 @@ Use Google Antigravity models directly in [Pi](https://pi.dev).
|
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
|
-
- **
|
|
13
|
-
|
|
12
|
+
- **Wire parity**: headers, request envelopes, and thinking/tier mapping
|
|
13
|
+
matched to the official Antigravity client.
|
|
14
14
|
- **Dynamic model catalog**: exposes exactly the models your account can use,
|
|
15
15
|
synthesized from the live catalog and cached for offline startup.
|
|
16
16
|
Newly released models are usable immediately without waiting for an extension update.
|
|
17
17
|
- **OAuth with auto-refresh**: log in once via `/login antigravity`; tokens refresh silently.
|
|
18
18
|
- **Quota visibility**: 5-hour and weekly remaining quota per model pool, with reset times
|
|
19
19
|
and a live footer slot. See [Quota footer](#quota-footer).
|
|
20
|
+
- **Search grounding**: built-in `search_web` tool and `/antigravity search` command
|
|
21
|
+
with source citations (no extra API keys required).
|
|
20
22
|
|
|
21
23
|
## Requirements
|
|
22
24
|
|
|
@@ -70,6 +72,7 @@ Extension subcommands (`/antigravity ...`):
|
|
|
70
72
|
| `models` | View available models and remaining fraction (alias: `model`) |
|
|
71
73
|
| `refresh` | Fetch the latest model list from Antigravity |
|
|
72
74
|
| `settings` | Configure provider settings (alias: `setting`) |
|
|
75
|
+
| `search` | Search the web using Google Search Grounding |
|
|
73
76
|
| `login` | Shortcut: fills in `/login antigravity` for you |
|
|
74
77
|
|
|
75
78
|
Related Pi builtins:
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
3
|
+
|
|
1
4
|
// src/auth.ts
|
|
2
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
6
|
|
|
@@ -251,8 +254,7 @@ function parseStoredCredentials(rawApiKey) {
|
|
|
251
254
|
return { token: rawApiKey, projectId: FALLBACK_PROJECT_ID };
|
|
252
255
|
}
|
|
253
256
|
|
|
254
|
-
// src/
|
|
255
|
-
var PRIVATE_SNAPSHOT_KEY = "pi-provider-antigravity";
|
|
257
|
+
// src/models.ts
|
|
256
258
|
var CANONICAL_TIER_SUFFIXES = Object.freeze({
|
|
257
259
|
low: "-low",
|
|
258
260
|
medium: "-medium",
|
|
@@ -282,6 +284,27 @@ function extractBaseModelId(runtimeId) {
|
|
|
282
284
|
if (runtimeId.startsWith("gemini-3.1-pro-")) return "gemini-3.1-pro";
|
|
283
285
|
return runtimeId.replace(TIER_SUFFIX_PATTERN, "");
|
|
284
286
|
}
|
|
287
|
+
var TIER_FALLBACKS = Object.freeze({
|
|
288
|
+
minimal: Object.freeze(["-low", "-extra-low", ""]),
|
|
289
|
+
low: Object.freeze(["-extra-low", ""]),
|
|
290
|
+
medium: Object.freeze(["", "-high"]),
|
|
291
|
+
// Gemini 3.1 Pro lists no -medium: up to high, never down
|
|
292
|
+
high: Object.freeze(["-thinking", "-agent", ""]),
|
|
293
|
+
// Claude's high tier is -thinking
|
|
294
|
+
xhigh: Object.freeze(["-high", "-thinking", "-agent", ""]),
|
|
295
|
+
max: Object.freeze(["-high", "-thinking", "-agent", ""])
|
|
296
|
+
});
|
|
297
|
+
var DEFAULT_TIER_ORDER = Object.freeze([
|
|
298
|
+
CANONICAL_TIER_SUFFIXES.high,
|
|
299
|
+
...TIER_ALIASES.high,
|
|
300
|
+
""
|
|
301
|
+
]);
|
|
302
|
+
function tierCandidateOrder(effort) {
|
|
303
|
+
if (!effort) return DEFAULT_TIER_ORDER;
|
|
304
|
+
const canonical = effort in CANONICAL_TIER_SUFFIXES ? [CANONICAL_TIER_SUFFIXES[effort]] : [`-${effort}`];
|
|
305
|
+
const fallbacks = TIER_FALLBACKS[effort] ?? DEFAULT_TIER_ORDER;
|
|
306
|
+
return [.../* @__PURE__ */ new Set([...canonical, ...fallbacks])];
|
|
307
|
+
}
|
|
285
308
|
function classifyModelFamily(modelId) {
|
|
286
309
|
const bare = ((modelId || "").split("/").pop() || "").toLowerCase();
|
|
287
310
|
if (bare.startsWith("gemini-")) return "gemini";
|
|
@@ -296,6 +319,9 @@ function isCompatibleFamily(msgModel, targetModelId) {
|
|
|
296
319
|
if (msgFamily !== "unknown" && msgFamily === classifyModelFamily(targetModelId)) return true;
|
|
297
320
|
return extractBaseModelId(msgModel) === extractBaseModelId(targetModelId);
|
|
298
321
|
}
|
|
322
|
+
|
|
323
|
+
// src/model-catalog.ts
|
|
324
|
+
var PRIVATE_SNAPSHOT_KEY = "pi-provider-antigravity";
|
|
299
325
|
function buildThinkingMap(models) {
|
|
300
326
|
return Object.fromEntries(
|
|
301
327
|
models.map((m) => [
|
|
@@ -326,62 +352,10 @@ function copySnapshot(snapshot) {
|
|
|
326
352
|
};
|
|
327
353
|
}
|
|
328
354
|
var EMPTY_SNAPSHOT = () => ({ enums: {}, runtimeIds: [], thinking: {}, deprecated: {} });
|
|
329
|
-
function createModelCatalog() {
|
|
330
|
-
let generation = { snapshot: EMPTY_SNAPSHOT(), version: 0 };
|
|
331
|
-
const record = (catalog) => {
|
|
332
|
-
generation = {
|
|
333
|
-
snapshot: snapshotFromCatalog(catalog),
|
|
334
|
-
items: catalog,
|
|
335
|
-
version: generation.version + 1
|
|
336
|
-
};
|
|
337
|
-
};
|
|
338
|
-
const restore = (snapshot) => {
|
|
339
|
-
if (generation.version > 0) return;
|
|
340
|
-
generation = { snapshot: copySnapshot(snapshot), version: 0 };
|
|
341
|
-
};
|
|
342
|
-
const refresh = async (context) => {
|
|
343
|
-
const persisted = fromPersistedSnapshot(context.stored?.[PRIVATE_SNAPSHOT_KEY]);
|
|
344
|
-
if (persisted) restore(persisted);
|
|
345
|
-
const storedModels = () => [...context.stored?.models ?? []];
|
|
346
|
-
if (!context.allowNetwork) return storedModels();
|
|
347
|
-
try {
|
|
348
|
-
const creds = await resolveCredentials(context);
|
|
349
|
-
if (!creds) return storedModels();
|
|
350
|
-
const catalog = await fetchAvailableModelsCatalog(creds, creds.projectId, context.signal);
|
|
351
|
-
record(catalog);
|
|
352
|
-
const dynamicModels = buildDynamicPublicModels(catalog);
|
|
353
|
-
if (context.publish) {
|
|
354
|
-
await context.publish({
|
|
355
|
-
persist: {
|
|
356
|
-
models: dynamicModels,
|
|
357
|
-
checkedAt: Date.now(),
|
|
358
|
-
[PRIVATE_SNAPSHOT_KEY]: toPersistedSnapshot(generation.snapshot)
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
return dynamicModels;
|
|
363
|
-
} catch {
|
|
364
|
-
return storedModels();
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
const refreshGen = async (doRefresh) => {
|
|
368
|
-
const versionBefore = generation.version;
|
|
369
|
-
await doRefresh();
|
|
370
|
-
const { items, version } = generation;
|
|
371
|
-
if (!items) return { status: "failed" };
|
|
372
|
-
return { status: version === versionBefore ? "stale" : "fresh", catalog: items };
|
|
373
|
-
};
|
|
374
|
-
return {
|
|
375
|
-
generation: () => ({ ...generation, snapshot: copySnapshot(generation.snapshot) }),
|
|
376
|
-
record,
|
|
377
|
-
restore,
|
|
378
|
-
refresh,
|
|
379
|
-
refreshGeneration: refreshGen,
|
|
380
|
-
resolvePlan: (publicModelId, effort) => resolveModelPlan(publicModelId, effort, generation.snapshot),
|
|
381
|
-
formatList: () => generation.items ? formatModelsList(generation.items) : "No models available."
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
355
|
function parseAvailableModels(data) {
|
|
356
|
+
if (!data || typeof data !== "object") {
|
|
357
|
+
return { models: [], modelEnums: {} };
|
|
358
|
+
}
|
|
385
359
|
const models = [];
|
|
386
360
|
const modelEnums = {};
|
|
387
361
|
const modelsObj = data.models || {};
|
|
@@ -468,27 +442,6 @@ function fromPersistedSnapshot(raw) {
|
|
|
468
442
|
if (!enums && !runtimeIds && !thinking && !deprecated) return void 0;
|
|
469
443
|
return { enums: enums ?? {}, runtimeIds: runtimeIds ?? [], thinking: thinking ?? {}, deprecated: deprecated ?? {} };
|
|
470
444
|
}
|
|
471
|
-
var TIER_FALLBACKS = Object.freeze({
|
|
472
|
-
minimal: Object.freeze(["-low", "-extra-low", ""]),
|
|
473
|
-
low: Object.freeze(["-extra-low", ""]),
|
|
474
|
-
medium: Object.freeze(["", "-high"]),
|
|
475
|
-
// Gemini 3.1 Pro lists no -medium: up to high, never down
|
|
476
|
-
high: Object.freeze(["-thinking", "-agent", ""]),
|
|
477
|
-
// Claude's high tier is -thinking
|
|
478
|
-
xhigh: Object.freeze(["-high", "-thinking", "-agent", ""]),
|
|
479
|
-
max: Object.freeze(["-high", "-thinking", "-agent", ""])
|
|
480
|
-
});
|
|
481
|
-
var DEFAULT_TIER_ORDER = Object.freeze([
|
|
482
|
-
CANONICAL_TIER_SUFFIXES.high,
|
|
483
|
-
...TIER_ALIASES.high,
|
|
484
|
-
""
|
|
485
|
-
]);
|
|
486
|
-
function tierCandidateOrder(effort) {
|
|
487
|
-
if (!effort) return DEFAULT_TIER_ORDER;
|
|
488
|
-
const canonical = effort in CANONICAL_TIER_SUFFIXES ? [CANONICAL_TIER_SUFFIXES[effort]] : [`-${effort}`];
|
|
489
|
-
const fallbacks = TIER_FALLBACKS[effort] ?? DEFAULT_TIER_ORDER;
|
|
490
|
-
return [.../* @__PURE__ */ new Set([...canonical, ...fallbacks])];
|
|
491
|
-
}
|
|
492
445
|
function resolveRuntimeModelId(modelId, effort, availableRuntimeIds) {
|
|
493
446
|
if (ALL_TIER_SUFFIXES.some((suffix) => modelId.endsWith(suffix))) {
|
|
494
447
|
return modelId;
|
|
@@ -507,6 +460,21 @@ function resolveRuntimeModelId(modelId, effort, availableRuntimeIds) {
|
|
|
507
460
|
}
|
|
508
461
|
return modelId;
|
|
509
462
|
}
|
|
463
|
+
function followRenames(runtimeModelId, snapshot) {
|
|
464
|
+
const renamed = snapshot.deprecated;
|
|
465
|
+
if (!renamed) return runtimeModelId;
|
|
466
|
+
let current = runtimeModelId;
|
|
467
|
+
const seen = /* @__PURE__ */ new Set([current]);
|
|
468
|
+
while (renamed[current] && !seen.has(renamed[current])) {
|
|
469
|
+
current = renamed[current];
|
|
470
|
+
seen.add(current);
|
|
471
|
+
}
|
|
472
|
+
return current;
|
|
473
|
+
}
|
|
474
|
+
function resolveThinkingConfig(runtimeModelId, snapshot) {
|
|
475
|
+
const budget = snapshot.thinking[runtimeModelId]?.budget;
|
|
476
|
+
return typeof budget === "number" ? { includeThoughts: true, thinkingBudget: budget } : { includeThoughts: false, thinkingBudget: 0 };
|
|
477
|
+
}
|
|
510
478
|
function resolveModelPlan(publicModelId, effort, snapshot) {
|
|
511
479
|
const runtimeModelId = followRenames(
|
|
512
480
|
resolveRuntimeModelId(publicModelId, effort, snapshot.runtimeIds),
|
|
@@ -527,24 +495,22 @@ function resolveModelPlan(publicModelId, effort, snapshot) {
|
|
|
527
495
|
isClaude: family === "claude"
|
|
528
496
|
};
|
|
529
497
|
}
|
|
530
|
-
function followRenames(runtimeModelId, snapshot) {
|
|
531
|
-
const renamed = snapshot.deprecated;
|
|
532
|
-
if (!renamed) return runtimeModelId;
|
|
533
|
-
let current = runtimeModelId;
|
|
534
|
-
const seen = /* @__PURE__ */ new Set([current]);
|
|
535
|
-
while (renamed[current] && !seen.has(renamed[current])) {
|
|
536
|
-
current = renamed[current];
|
|
537
|
-
seen.add(current);
|
|
538
|
-
}
|
|
539
|
-
return current;
|
|
540
|
-
}
|
|
541
|
-
function resolveThinkingConfig(runtimeModelId, snapshot) {
|
|
542
|
-
const budget = snapshot.thinking[runtimeModelId]?.budget;
|
|
543
|
-
return typeof budget === "number" ? { includeThoughts: true, thinkingBudget: budget } : { includeThoughts: false, thinkingBudget: 0 };
|
|
544
|
-
}
|
|
545
498
|
function estimateModelCost(_baseId) {
|
|
546
499
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
547
500
|
}
|
|
501
|
+
function formatModelDisplayName(baseId, rawDisplayName) {
|
|
502
|
+
if (rawDisplayName) {
|
|
503
|
+
const cleaned = rawDisplayName.replace(/\s*\([^)]*\)/g, "").trim();
|
|
504
|
+
if (cleaned.length > 0) {
|
|
505
|
+
return cleaned;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const words = baseId.split("-").map((w) => {
|
|
509
|
+
if (/^\d+(\.\d+)?$/.test(w)) return w;
|
|
510
|
+
return w.charAt(0).toUpperCase() + w.slice(1);
|
|
511
|
+
});
|
|
512
|
+
return words.join(" ");
|
|
513
|
+
}
|
|
548
514
|
function synthesizeDynamicModel(baseId, items) {
|
|
549
515
|
const repItem = items.find((it) => it.id === `${baseId}-high` || it.id === baseId) || items[0];
|
|
550
516
|
const hasVariant = (suffixes) => items.some((it) => suffixes.some((suffix) => it.id.endsWith(suffix)));
|
|
@@ -604,19 +570,6 @@ function buildDynamicPublicModels(catalog) {
|
|
|
604
570
|
}
|
|
605
571
|
return orderedBaseIds.map((baseId) => synthesizeDynamicModel(baseId, runtimeGroups.get(baseId)));
|
|
606
572
|
}
|
|
607
|
-
function formatModelDisplayName(baseId, rawDisplayName) {
|
|
608
|
-
if (rawDisplayName) {
|
|
609
|
-
const cleaned = rawDisplayName.replace(/\s*\([^)]*\)/g, "").trim();
|
|
610
|
-
if (cleaned.length > 0) {
|
|
611
|
-
return cleaned;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
const words = baseId.split("-").map((w) => {
|
|
615
|
-
if (/^\d+(\.\d+)?$/.test(w)) return w;
|
|
616
|
-
return w.charAt(0).toUpperCase() + w.slice(1);
|
|
617
|
-
});
|
|
618
|
-
return words.join(" ");
|
|
619
|
-
}
|
|
620
573
|
function formatTokenCount(n) {
|
|
621
574
|
if (typeof n !== "number") return "N/A";
|
|
622
575
|
if (n % 1048576 === 0) return `${n / 1048576}M`;
|
|
@@ -661,6 +614,82 @@ function formatModelsList(catalog) {
|
|
|
661
614
|
}
|
|
662
615
|
return lines.join("\n");
|
|
663
616
|
}
|
|
617
|
+
function createModelCatalog() {
|
|
618
|
+
let generation = { snapshot: EMPTY_SNAPSHOT(), version: 0 };
|
|
619
|
+
const record = (catalogOrRaw) => {
|
|
620
|
+
let catalog;
|
|
621
|
+
if (catalogOrRaw && typeof catalogOrRaw === "object" && "models" in catalogOrRaw && Array.isArray(catalogOrRaw.models) && "modelEnums" in catalogOrRaw && typeof catalogOrRaw.modelEnums === "object") {
|
|
622
|
+
catalog = catalogOrRaw;
|
|
623
|
+
} else {
|
|
624
|
+
catalog = parseAvailableModels(catalogOrRaw);
|
|
625
|
+
}
|
|
626
|
+
generation = {
|
|
627
|
+
snapshot: snapshotFromCatalog(catalog),
|
|
628
|
+
items: catalog,
|
|
629
|
+
version: generation.version + 1
|
|
630
|
+
};
|
|
631
|
+
};
|
|
632
|
+
const restore = (persistedOrSnapshot) => {
|
|
633
|
+
if (generation.version > 0) return;
|
|
634
|
+
if (!persistedOrSnapshot || typeof persistedOrSnapshot !== "object") return;
|
|
635
|
+
if ("enums" in persistedOrSnapshot && "runtimeIds" in persistedOrSnapshot && Array.isArray(persistedOrSnapshot.runtimeIds)) {
|
|
636
|
+
generation = { snapshot: copySnapshot(persistedOrSnapshot), version: 0 };
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const snapshot = fromPersistedSnapshot(persistedOrSnapshot);
|
|
640
|
+
if (snapshot) {
|
|
641
|
+
generation = { snapshot: copySnapshot(snapshot), version: 0 };
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
const toPersisted = () => {
|
|
645
|
+
if (generation.version === 0 && generation.snapshot.runtimeIds.length === 0) {
|
|
646
|
+
return void 0;
|
|
647
|
+
}
|
|
648
|
+
return toPersistedSnapshot(generation.snapshot);
|
|
649
|
+
};
|
|
650
|
+
const refresh = async (context) => {
|
|
651
|
+
const persisted = context.stored?.[PRIVATE_SNAPSHOT_KEY];
|
|
652
|
+
if (persisted) restore(persisted);
|
|
653
|
+
const storedModels = () => [...context.stored?.models ?? []];
|
|
654
|
+
if (!context.allowNetwork) return storedModels();
|
|
655
|
+
try {
|
|
656
|
+
const creds = await resolveCredentials(context);
|
|
657
|
+
if (!creds) return storedModels();
|
|
658
|
+
const catalog = await fetchAvailableModelsCatalog(creds, creds.projectId, context.signal);
|
|
659
|
+
record(catalog);
|
|
660
|
+
const dynamicModels = buildDynamicPublicModels(catalog);
|
|
661
|
+
if (context.publish) {
|
|
662
|
+
await context.publish({
|
|
663
|
+
persist: {
|
|
664
|
+
models: dynamicModels,
|
|
665
|
+
checkedAt: Date.now(),
|
|
666
|
+
[PRIVATE_SNAPSHOT_KEY]: toPersisted()
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
return dynamicModels;
|
|
671
|
+
} catch {
|
|
672
|
+
return storedModels();
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
const refreshGen = async (doRefresh) => {
|
|
676
|
+
const versionBefore = generation.version;
|
|
677
|
+
await doRefresh();
|
|
678
|
+
const { items, version } = generation;
|
|
679
|
+
if (!items) return { status: "failed" };
|
|
680
|
+
return { status: version === versionBefore ? "stale" : "fresh", catalog: items };
|
|
681
|
+
};
|
|
682
|
+
return {
|
|
683
|
+
generation: () => ({ ...generation, snapshot: copySnapshot(generation.snapshot) }),
|
|
684
|
+
record,
|
|
685
|
+
restore,
|
|
686
|
+
toPersisted,
|
|
687
|
+
refresh,
|
|
688
|
+
refreshGeneration: refreshGen,
|
|
689
|
+
resolvePlan: (publicModelId, effort) => resolveModelPlan(publicModelId, effort, generation.snapshot),
|
|
690
|
+
formatList: () => generation.items ? formatModelsList(generation.items) : "No models available."
|
|
691
|
+
};
|
|
692
|
+
}
|
|
664
693
|
|
|
665
694
|
// src/quota-status.ts
|
|
666
695
|
import * as fs from "node:fs";
|
|
@@ -859,6 +888,14 @@ function validPairs(raw) {
|
|
|
859
888
|
}
|
|
860
889
|
return Object.keys(pairs).length > 0 ? pairs : void 0;
|
|
861
890
|
}
|
|
891
|
+
function isLiveCtx(ctx) {
|
|
892
|
+
try {
|
|
893
|
+
void ctx.ui;
|
|
894
|
+
return true;
|
|
895
|
+
} catch {
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
862
899
|
var FOOTER_MODES = Object.freeze({
|
|
863
900
|
off: {
|
|
864
901
|
key: "off",
|
|
@@ -1157,6 +1194,7 @@ var QuotaStatusCoordinator = class {
|
|
|
1157
1194
|
return !!this.summary && Date.now() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;
|
|
1158
1195
|
}
|
|
1159
1196
|
refresh(ctx, opts = {}) {
|
|
1197
|
+
if (!isLiveCtx(ctx)) return Promise.resolve(void 0);
|
|
1160
1198
|
if (!opts.ignoreMode && (this.mode() === "off" || !isAntigravityModel(ctx.model))) {
|
|
1161
1199
|
return Promise.resolve(void 0);
|
|
1162
1200
|
}
|
|
@@ -1174,6 +1212,7 @@ var QuotaStatusCoordinator = class {
|
|
|
1174
1212
|
}
|
|
1175
1213
|
// Paints current cached quota status to the footer slot.
|
|
1176
1214
|
paint(ctx) {
|
|
1215
|
+
if (!isLiveCtx(ctx)) return;
|
|
1177
1216
|
const mode = this.mode();
|
|
1178
1217
|
if (mode === "off" || !isAntigravityModel(ctx.model)) {
|
|
1179
1218
|
ctx.ui.setStatus(QUOTA_STATUS_KEY, void 0);
|
|
@@ -1866,6 +1905,102 @@ function streamAntigravity(model, context, options, catalog) {
|
|
|
1866
1905
|
return stream;
|
|
1867
1906
|
}
|
|
1868
1907
|
|
|
1908
|
+
// src/search.ts
|
|
1909
|
+
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.";
|
|
1910
|
+
var SEARCH_MODEL = "gemini-3.1-flash-lite";
|
|
1911
|
+
function formatSearchResults(rawText, metadata, query, createdAt, completedAt) {
|
|
1912
|
+
const sources = (metadata?.groundingChunks || []).map((chunk, i) => ({
|
|
1913
|
+
index: i + 1,
|
|
1914
|
+
title: chunk.web?.title || "Web Source",
|
|
1915
|
+
url: chunk.web?.uri || ""
|
|
1916
|
+
})).filter((s) => Boolean(s.url));
|
|
1917
|
+
const queries = metadata?.webSearchQueries || [];
|
|
1918
|
+
let annotatedText = rawText;
|
|
1919
|
+
const supports = (metadata?.groundingSupports || []).filter(
|
|
1920
|
+
(s) => typeof s.segment?.endIndex === "number" && Array.isArray(s.groundingChunkIndices) && s.groundingChunkIndices.length > 0
|
|
1921
|
+
).slice().sort((a, b) => (b.segment.endIndex ?? 0) - (a.segment.endIndex ?? 0));
|
|
1922
|
+
for (const sup of supports) {
|
|
1923
|
+
const end = sup.segment.endIndex;
|
|
1924
|
+
if (end >= 0 && end <= annotatedText.length) {
|
|
1925
|
+
const citations = (sup.groundingChunkIndices || []).map((idx) => `[${idx + 1}]`).join("");
|
|
1926
|
+
annotatedText = annotatedText.slice(0, end) + citations + annotatedText.slice(end);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
const sourceLines = sources.map((s) => `[${s.index}] [${s.title}](${s.url})`);
|
|
1930
|
+
let formattedOutput = `Created At: ${createdAt}
|
|
1931
|
+
Completed At: ${completedAt}
|
|
1932
|
+
The search for "${query}" returned the following summary:
|
|
1933
|
+
${annotatedText.trim() || "No content returned."}`;
|
|
1934
|
+
if (sourceLines.length > 0) {
|
|
1935
|
+
formattedOutput += `
|
|
1936
|
+
|
|
1937
|
+
Sources:
|
|
1938
|
+
${sourceLines.join("\n")}`;
|
|
1939
|
+
}
|
|
1940
|
+
return {
|
|
1941
|
+
query,
|
|
1942
|
+
rawText,
|
|
1943
|
+
formattedOutput,
|
|
1944
|
+
sources,
|
|
1945
|
+
queries,
|
|
1946
|
+
createdAt,
|
|
1947
|
+
completedAt
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
async function performWebSearch(creds, options) {
|
|
1951
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1952
|
+
let effectiveQuery = options.query.trim();
|
|
1953
|
+
if (options.domain && options.domain.trim()) {
|
|
1954
|
+
const d = options.domain.trim();
|
|
1955
|
+
if (!effectiveQuery.toLowerCase().includes(`site:${d.toLowerCase()}`)) {
|
|
1956
|
+
effectiveQuery = `${effectiveQuery} site:${d}`;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
const reqBody = {
|
|
1960
|
+
project: creds.projectId || FALLBACK_PROJECT_ID,
|
|
1961
|
+
request: {
|
|
1962
|
+
contents: [
|
|
1963
|
+
{
|
|
1964
|
+
role: "user",
|
|
1965
|
+
parts: [{ text: effectiveQuery }]
|
|
1966
|
+
}
|
|
1967
|
+
],
|
|
1968
|
+
systemInstruction: {
|
|
1969
|
+
role: "user",
|
|
1970
|
+
parts: [{ text: SEARCH_SYSTEM_INSTRUCTION }]
|
|
1971
|
+
},
|
|
1972
|
+
tools: [
|
|
1973
|
+
{
|
|
1974
|
+
googleSearch: {
|
|
1975
|
+
enhancedContent: {
|
|
1976
|
+
imageSearch: {
|
|
1977
|
+
maxResultCount: 5
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
],
|
|
1983
|
+
generationConfig: {
|
|
1984
|
+
candidateCount: 1
|
|
1985
|
+
}
|
|
1986
|
+
},
|
|
1987
|
+
model: SEARCH_MODEL,
|
|
1988
|
+
userAgent: "antigravity",
|
|
1989
|
+
requestType: "web_search"
|
|
1990
|
+
};
|
|
1991
|
+
const resp = await postAntigravityJson({
|
|
1992
|
+
auth: creds.token,
|
|
1993
|
+
path: "v1internal:generateContent",
|
|
1994
|
+
body: reqBody,
|
|
1995
|
+
signal: options.signal
|
|
1996
|
+
});
|
|
1997
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1998
|
+
const candidate = resp.response?.candidates?.[0];
|
|
1999
|
+
const rawText = candidate?.content?.parts?.filter((p) => typeof p.text === "string").map((p) => p.text).join("") || "";
|
|
2000
|
+
const metadata = candidate?.groundingMetadata;
|
|
2001
|
+
return formatSearchResults(rawText, metadata, effectiveQuery, createdAt, completedAt);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1869
2004
|
// src/commands.ts
|
|
1870
2005
|
function emitOutput(ctx, text, type = "info") {
|
|
1871
2006
|
if (ctx.hasUI) {
|
|
@@ -1898,6 +2033,11 @@ var SUBCOMMANDS = Object.freeze([
|
|
|
1898
2033
|
aliases: Object.freeze(["setting"]),
|
|
1899
2034
|
run: async ({ ctx, quotaStatus }) => openSettings(ctx, quotaStatus)
|
|
1900
2035
|
},
|
|
2036
|
+
{
|
|
2037
|
+
name: "search",
|
|
2038
|
+
description: "Search the web using Google Search Grounding",
|
|
2039
|
+
run: async ({ ctx, subArgs }) => runSearchSubcommand(ctx, subArgs)
|
|
2040
|
+
},
|
|
1901
2041
|
{
|
|
1902
2042
|
name: "login",
|
|
1903
2043
|
description: "Run /login antigravity",
|
|
@@ -2003,12 +2143,36 @@ function runLoginSubcommand(ctx) {
|
|
|
2003
2143
|
emitOutput(ctx, "Please run /login antigravity to authenticate.");
|
|
2004
2144
|
}
|
|
2005
2145
|
}
|
|
2146
|
+
async function runSearchSubcommand(ctx, query) {
|
|
2147
|
+
const creds = await resolveCredentials(ctx);
|
|
2148
|
+
if (!creds) {
|
|
2149
|
+
emitOutput(ctx, NOT_LOGGED_IN, "warning");
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
const trimmedQuery = (query || "").trim();
|
|
2153
|
+
if (!trimmedQuery) {
|
|
2154
|
+
emitOutput(ctx, "Usage: /antigravity search <query>", "warning");
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
try {
|
|
2158
|
+
if (ctx.hasUI) ctx.ui.notify(`Searching: "${trimmedQuery}"\u2026`, "info");
|
|
2159
|
+
const result = await performWebSearch(creds, {
|
|
2160
|
+
query: trimmedQuery,
|
|
2161
|
+
signal: ctx.signal
|
|
2162
|
+
});
|
|
2163
|
+
emitOutput(ctx, result.formattedOutput);
|
|
2164
|
+
} catch (err) {
|
|
2165
|
+
emitOutput(ctx, `Search failed: ${err.message}`, "error");
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2006
2168
|
async function runAntigravitySubcommand(args, ctx, quotaStatus, catalog) {
|
|
2007
|
-
const
|
|
2169
|
+
const raw = (args || "").trim();
|
|
2170
|
+
const parts = raw.split(/\s+/).filter(Boolean);
|
|
2008
2171
|
const sub = parseAntigravitySubcommand(parts[0] || "");
|
|
2172
|
+
const subArgs = raw.slice(parts[0]?.length || 0).trim();
|
|
2009
2173
|
const cmd = SUBCOMMANDS.find((c) => c.name === sub);
|
|
2010
2174
|
if (cmd) {
|
|
2011
|
-
await cmd.run({ ctx, quotaStatus, catalog });
|
|
2175
|
+
await cmd.run({ ctx, quotaStatus, catalog, subArgs });
|
|
2012
2176
|
return;
|
|
2013
2177
|
}
|
|
2014
2178
|
emitOutput(ctx, USAGE_TEXT);
|
|
@@ -2041,6 +2205,53 @@ function index_default(pi) {
|
|
|
2041
2205
|
await runAntigravitySubcommand(args, ctx, quotaStatus, catalog);
|
|
2042
2206
|
}
|
|
2043
2207
|
});
|
|
2208
|
+
if (typeof pi.registerTool === "function") {
|
|
2209
|
+
pi.registerTool({
|
|
2210
|
+
name: "search_web",
|
|
2211
|
+
label: "Search Web",
|
|
2212
|
+
description: "Performs a web search for a given query. Returns a summary of relevant information along with URL citations.",
|
|
2213
|
+
promptSnippet: "Search the web using Google Search Grounding with source citations",
|
|
2214
|
+
promptGuidelines: [
|
|
2215
|
+
"Use search_web when you need real-time web search, latest documentation, or up-to-date facts with verified citations."
|
|
2216
|
+
],
|
|
2217
|
+
parameters: Type.Object({
|
|
2218
|
+
query: Type.String({ description: "The search query string to look up on the web." }),
|
|
2219
|
+
domain: Type.Optional(
|
|
2220
|
+
Type.String({
|
|
2221
|
+
description: "Optional domain to recommend the search prioritize (e.g. 'github.com', 'developer.mozilla.org')."
|
|
2222
|
+
})
|
|
2223
|
+
)
|
|
2224
|
+
}),
|
|
2225
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
2226
|
+
const creds = await resolveCredentials(ctx);
|
|
2227
|
+
if (!creds) {
|
|
2228
|
+
return {
|
|
2229
|
+
content: [{ type: "text", text: "Error: Not logged in. Run /login antigravity first." }],
|
|
2230
|
+
details: { error: "not_logged_in" }
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
try {
|
|
2234
|
+
const result = await performWebSearch(creds, {
|
|
2235
|
+
query: params.query,
|
|
2236
|
+
domain: params.domain,
|
|
2237
|
+
signal
|
|
2238
|
+
});
|
|
2239
|
+
return {
|
|
2240
|
+
content: [{ type: "text", text: result.formattedOutput }],
|
|
2241
|
+
details: {
|
|
2242
|
+
sources: result.sources,
|
|
2243
|
+
queries: result.queries
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
} catch (err) {
|
|
2247
|
+
return {
|
|
2248
|
+
content: [{ type: "text", text: `Search failed: ${err.message}` }],
|
|
2249
|
+
details: { error: err.message }
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2044
2255
|
pi.on("session_start", async (_event, ctx) => {
|
|
2045
2256
|
await quotaStatus.refreshAndPaint(ctx);
|
|
2046
2257
|
});
|