@estebanforge/pi-ask-antigravity 1.0.0 → 1.0.1

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
@@ -2,7 +2,9 @@
2
2
 
3
3
  A [Pi](https://github.com/earendil-works/pi-coding-agent) extension that exposes the **`AskAntigravity`** tool: delegate a self-contained sub-task to Google Antigravity's `agy` CLI and stream its response back into the Pi session.
4
4
 
5
- It is the `AskClaude`-style delegation pattern, pointed at Gemini via `agy`. The tool answers to three names the CLI is known by — **gemini**, **antigravity**, and **agy** — surfaced in its description so the model maps any of them to this single tool.
5
+ It is the `AskClaude`-style delegation pattern (from pi-claude-bride extension), pointed at Gemini via `agy`. The tool answers to three names the CLI is known by — **gemini**, **antigravity**, and **agy** — surfaced in its description so the model maps any of them to this single tool.
6
+
7
+ <img width="1512" height="845" alt="image" src="https://github.com/user-attachments/assets/3e1a8f19-64d2-43ab-a30f-47f39daa151d" />
6
8
 
7
9
  ## Install
8
10
 
@@ -37,7 +37,7 @@ import {
37
37
  getSettingsListTheme,
38
38
  type ExtensionAPI,
39
39
  } from "@earendil-works/pi-coding-agent";
40
- import { StringEnum } from "@earendil-works/pi-ai";
40
+
41
41
  import { Container, SettingsList, Text, type SettingItem } from "@earendil-works/pi-tui";
42
42
  import { Type } from "typebox";
43
43
 
@@ -252,14 +252,27 @@ function resolveModel(
252
252
  const versioned = candidates.filter((e) => e.version === version);
253
253
  if (versioned.length > 0) candidates = versioned;
254
254
  } else {
255
- const versions = candidates
256
- .map((e) => e.version)
257
- .filter((v): v is string => v !== null)
258
- .sort(compareVersionsDesc);
259
- if (versions.length > 0) {
260
- const top = versions[0];
261
- const latest = candidates.filter((e) => e.version === top || e.version === null);
262
- if (latest.length > 0) candidates = latest;
255
+ // Prefer Google's official `gemini-*-latest` aliases (entries with no
256
+ // parseable version in their name, e.g. "Gemini Flash Latest") when
257
+ // the user did NOT pin a specific version. The alias is the
258
+ // versionless pointer Google intends for "the current release" and
259
+ // hot-swaps on every release, while versioned entries like
260
+ // "Gemini 3.6 Flash (Medium)" stay available via explicit pinning
261
+ // (e.g. "3.6 flash medium"). Falls back to the highest versioned
262
+ // entry if no alias is present in the catalog.
263
+ const aliases = candidates.filter((e) => e.version === null);
264
+ if (aliases.length > 0) {
265
+ candidates = aliases;
266
+ } else {
267
+ const versions = candidates
268
+ .map((e) => e.version)
269
+ .filter((v): v is string => v !== null)
270
+ .sort(compareVersionsDesc);
271
+ if (versions.length > 0) {
272
+ const top = versions[0];
273
+ const latest = candidates.filter((e) => e.version === top);
274
+ if (latest.length > 0) candidates = latest;
275
+ }
263
276
  }
264
277
  }
265
278
 
@@ -382,8 +395,9 @@ interface AgyDetails {
382
395
 
383
396
  export default async function (pi: ExtensionAPI) {
384
397
  const binary = resolveAgy();
385
- // Discovered once at load; frozen for the session (including the
386
- // StringEnum model param). Run /reload after an `agy update` to refresh.
398
+ // Discovered once at load; frozen for the session. Run /reload after an
399
+ // `agy update` to refresh. Failure is non-fatal: resolveModel falls back
400
+ // to passthrough so exact slugs typed by the user still work.
387
401
  const discovered = await discoverModels(binary).catch(() => []);
388
402
 
389
403
  // --- /agy: view / change default model + thinking ---------------------
@@ -498,27 +512,17 @@ export default async function (pi: ExtensionAPI) {
498
512
 
499
513
  // --- Tool registration -------------------------------------------------
500
514
 
501
- // Model param: free string (friendly alias OR exact). We surface the
502
- // discovered models as a StringEnum when available so the model gets a
503
- // bounded list, but also accept arbitrary input that resolves through
504
- // the alias map. Description carries the alias grammar.
505
- const modelParam = discovered.length
506
- ? Type.Optional(
507
- StringEnum(
508
- // Offer friendly aliases plus the full exact strings.
509
- [...new Set([...MODEL_OPTIONS, ...discovered.map((e) => e.full)])],
510
- {
511
- description:
512
- "Model alias or exact id. Friendly: 'flash' (latest Flash), 'pro' (latest Pro), 'gemini' (=flash). Add a tier: 'flash high', 'pro low'. Pin a version: '3.5 flash'. Exact: 'Gemini 3.5 Flash (Medium)'. Omit for the configured default.",
513
- },
514
- ),
515
- )
516
- : Type.Optional(
517
- Type.String({
518
- description:
519
- "Model alias or exact id. Friendly: 'flash', 'pro', 'gemini'. Add tier: 'flash high'. Omit for default.",
520
- }),
521
- );
515
+ // Model param: free string (friendly alias OR exact). Previously this
516
+ // was a StringEnum built from the live catalog; that made tiered/pinned
517
+ // aliases ("flash high", "3.5 flash") fail AJV validation before
518
+ // resolveModel ever saw them. Type.String() lets the resolver handle
519
+ // every documented form and falls back to agy for unknown slugs.
520
+ const modelParam = Type.Optional(
521
+ Type.String({
522
+ description:
523
+ "Model alias or exact id. Friendly: 'flash' (latest Flash, prefers the gemini-flash-latest alias), 'pro' (latest Pro, prefers gemini-pro-latest), 'gemini' (=flash). Add a tier: 'flash high', 'pro low'. Pin a version: '3.5 flash'. Exact: 'Gemini 3.5 Flash (Medium)'. Omit for the configured default.",
524
+ }),
525
+ );
522
526
 
523
527
  pi.registerTool({
524
528
  name: "AskAntigravity",
@@ -581,6 +585,21 @@ export default async function (pi: ExtensionAPI) {
581
585
 
582
586
  const config = loadConfig();
583
587
  const requestedModel = (params.model as string | undefined) ?? config.defaultModel;
588
+ // Defensive: reject leading-dash model values that could misbind
589
+ // on agy's arg parser when spliced as the `--model` value. Same
590
+ // threat model as CONV_ID_RE — a leading-dash value can't be a
591
+ // model id, so refuse it instead of letting it reach argv.
592
+ if (typeof params.model === "string" && params.model.trim().startsWith("-")) {
593
+ return {
594
+ content: [
595
+ {
596
+ type: "text",
597
+ text: `model value "${params.model}" starts with "-" — not a valid model id. Use a friendly alias (e.g. "flash", "pro", "gemini") or a known exact id (e.g. "Gemini 3.5 Flash (Medium)").`,
598
+ },
599
+ ],
600
+ details: emptyDetails(requestedModel, null),
601
+ };
602
+ }
584
603
  const resolved =
585
604
  resolveModel(requestedModel, discovered, config.defaultThinking) ?? requestedModel;
586
605
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estebanforge/pi-ask-antigravity",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Pi tool that delegates a self-contained sub-task to Google Antigravity's agy CLI (the CLI for Gemini). Friendly model aliases (flash/pro/gemini), configurable default model + thinking, and the AskAntigravity delegation tool.",
5
5
  "keywords": [
6
6
  "pi-package",