@estebanforge/pi-ask-antigravity 1.2.1 → 1.2.3

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.
Files changed (2) hide show
  1. package/extensions/index.ts +63 -35
  2. package/package.json +1 -1
@@ -83,13 +83,13 @@ type Mode = "plan" | "accept-edits";
83
83
  // whose full string equals the overlay target, the live entry wins. When it
84
84
  // does not (older agy, missing model, plan gate), the overlay entry resolves
85
85
  // the alias so the user can still type "sonnet" and get a working answer.
86
- // "sonnet" -> Claude Sonnet 4.6 (Thinking)
87
- // "opus" -> Claude Opus 4.6 (Thinking)
88
- // "gpt-oss" -> GPT-OSS 120B (Medium)
86
+ // "sonnet" -> claude-sonnet-4-6
87
+ // "opus" -> claude-opus-4-6-thinking
88
+ // "gpt-oss" -> gpt-oss-120b-medium
89
89
  const STATIC_ALIAS_OVERLAY: ReadonlyArray<ModelEntry> = [
90
- { full: "Claude Sonnet 4.6 (Thinking)", family: "other", version: null, tier: null },
91
- { full: "Claude Opus 4.6 (Thinking)", family: "other", version: null, tier: null },
92
- { full: "GPT-OSS 120B (Medium)", family: "other", version: null, tier: null },
90
+ { full: "claude-sonnet-4-6", family: "other", version: null, tier: null },
91
+ { full: "claude-opus-4-6-thinking", family: "other", version: null, tier: null },
92
+ { full: "gpt-oss-120b-medium", family: "other", version: null, tier: null },
93
93
  ];
94
94
 
95
95
  // Short alias → overlay full string. Used by resolveModel to recognize
@@ -97,15 +97,15 @@ const STATIC_ALIAS_OVERLAY: ReadonlyArray<ModelEntry> = [
97
97
  // not match. The overlay entries are also merged into the live catalog for
98
98
  // exact-string passthrough, so this map only needs to cover the short names.
99
99
  const STATIC_SHORT_ALIAS: ReadonlyMap<string, string> = new Map([
100
- ["sonnet", "Claude Sonnet 4.6 (Thinking)"],
101
- ["opus", "Claude Opus 4.6 (Thinking)"],
102
- ["gpt-oss", "GPT-OSS 120B (Medium)"],
100
+ ["sonnet", "claude-sonnet-4-6"],
101
+ ["opus", "claude-opus-4-6-thinking"],
102
+ ["gpt-oss", "gpt-oss-120b-medium"],
103
103
  ]);
104
104
 
105
105
  /** Merge the live catalog with the static alias overlay. Live entries win on
106
106
  * case-insensitive full-string equality so an updated `agy models` listing
107
107
  * always takes precedence over the hardcoded fallback. */
108
- function mergeCatalog(live: ModelEntry[]): ModelEntry[] {
108
+ export function mergeCatalog(live: ModelEntry[]): ModelEntry[] {
109
109
  const seen = new Set(live.map((e) => e.full.toLowerCase()));
110
110
  const merged = [...live];
111
111
  for (const entry of STATIC_ALIAS_OVERLAY) {
@@ -139,13 +139,22 @@ COMPACT OUTPUT (param: digest): when true, the prompt is prefixed to request com
139
139
  type ThinkingTier = "low" | "medium" | "high";
140
140
  type Family = "flash" | "pro" | "other";
141
141
 
142
- interface ModelEntry {
143
- full: string; // exact agy string, e.g. "Gemini 3.5 Flash (Medium)"
142
+ export interface ModelEntry {
143
+ full: string; // exact agy slug, e.g. "gemini-3.6-flash-medium"
144
144
  family: Family;
145
- version: string | null; // "3.5"
145
+ version: string | null; // "3.6"
146
146
  tier: ThinkingTier | null;
147
147
  }
148
148
 
149
+ /** Argv-facing model resolution: the exact --model slug plus an optional
150
+ * --effort tier. Gemini bases split the tier out (the base slug alone is
151
+ * invalid without --effort); fixed-thinking families keep agy's exact slug
152
+ * and carry no effort. */
153
+ export interface ResolvedModel {
154
+ model: string;
155
+ effort?: ThinkingTier;
156
+ }
157
+
149
158
  interface Config {
150
159
  defaultModel: string;
151
160
  defaultThinking: ThinkingTier;
@@ -243,8 +252,11 @@ function saveConfig(patch: Partial<Config>): SaveResult {
243
252
  // --- Model parsing + alias resolution --------------------------------------
244
253
 
245
254
  /** Parse one `agy models` line into a structured entry. */
246
- function parseModelLine(line: string): ModelEntry | null {
247
- const full = line.trim();
255
+ export function parseModelLine(line: string): ModelEntry | null {
256
+ // agy prints TWO columns: "<slug> <display label>". --model takes only the
257
+ // slug (col 1), so split it off; the label is display-only. A bare-slug line
258
+ // (no whitespace) splits to itself.
259
+ const full = line.trim().split(/\s+/)[0] ?? "";
248
260
  if (!full) return null;
249
261
 
250
262
  const lower = full.toLowerCase();
@@ -257,7 +269,7 @@ function parseModelLine(line: string): ModelEntry | null {
257
269
  const versionMatch = lower.match(/(\d+\.\d+)/);
258
270
  const version = versionMatch ? versionMatch[1] : null;
259
271
 
260
- const tierMatch = lower.match(/\((low|medium|high)\)/);
272
+ const tierMatch = lower.match(/-(low|medium|high)$/);
261
273
  const tier = tierMatch ? (tierMatch[1] as ThinkingTier) : null;
262
274
 
263
275
  return { full, family, version, tier };
@@ -276,21 +288,34 @@ function nearestTier(available: ThinkingTier[], preferred: ThinkingTier): Thinki
276
288
  return sorted[0] ?? preferred;
277
289
  }
278
290
 
291
+ /** Build the argv-facing resolution from a picked catalog entry. Gemini bases
292
+ * (slugs starting "gemini-") accept a separate --effort, so split the tier
293
+ * suffix out of the slug: the base alone (gemini-3.6-flash) is what --model
294
+ * wants, and the tier goes to --effort. Fixed-thinking families keep agy's
295
+ * exact slug even when it carries a -medium suffix (gpt-oss-120b-medium):
296
+ * agy rejects --effort for them, so the suffix stays part of the slug. */
297
+ function toResolved(full: string, tier: ThinkingTier | null): ResolvedModel {
298
+ if (tier && full.toLowerCase().startsWith("gemini-")) {
299
+ return { model: full.replace(/-(low|medium|high)$/, ""), effort: tier };
300
+ }
301
+ return { model: full };
302
+ }
303
+
279
304
  /**
280
- * Resolve a friendly alias / partial name to an exact agy model string.
281
- * Returns null if resolution is not possible (caller passes input through
282
- * to agy, which may resolve or fail on its own).
305
+ * Resolve a friendly alias / partial name to an argv-facing {model, effort?}.
306
+ * Returns null only when the family is unrecognized; the caller then passes
307
+ * the raw input straight to agy.
283
308
  */
284
- function resolveModel(
309
+ export function resolveModel(
285
310
  input: string,
286
311
  entries: ModelEntry[],
287
312
  defaultThinking: ThinkingTier,
288
- ): string | null {
313
+ ): ResolvedModel | null {
289
314
  const lower = input.toLowerCase().trim();
290
315
 
291
316
  // 1. Exact full-string match (case-insensitive).
292
317
  const exact = entries.find((e) => e.full.toLowerCase() === lower);
293
- if (exact) return exact.full;
318
+ if (exact) return toResolved(exact.full, exact.tier);
294
319
 
295
320
  // 1b. Static short alias ("sonnet" / "opus" / "gpt-oss"). Checked
296
321
  // before the family parser because none of these names contain
@@ -303,9 +328,8 @@ function resolveModel(
303
328
  // the model under different casing than the overlay.
304
329
  if (STATIC_SHORT_ALIAS.has(lower)) {
305
330
  const target = STATIC_SHORT_ALIAS.get(lower) as string;
306
- const targetLower = target.toLowerCase();
307
- const fromCatalog = entries.find((e) => e.full.toLowerCase() === targetLower);
308
- return fromCatalog ? fromCatalog.full : target;
331
+ const fromCatalog = entries.find((e) => e.full.toLowerCase() === target.toLowerCase());
332
+ return toResolved(fromCatalog?.full ?? target, fromCatalog?.tier ?? null);
309
333
  }
310
334
 
311
335
  // 2. Parse the alias.
@@ -365,13 +389,14 @@ function resolveModel(
365
389
  const familyTiers = new Set(
366
390
  candidates.map((e) => e.tier).filter((t): t is ThinkingTier => t !== null),
367
391
  );
368
- if (familyTiers.size === 0) return candidates[0].full; // no tiers on any entry
392
+ if (familyTiers.size === 0) return toResolved(candidates[0].full, null); // no tiers on any entry
369
393
 
370
394
  const preferred =
371
395
  tier ??
372
396
  (familyTiers.has(defaultThinking) ? defaultThinking : FAMILY_DEFAULT_TIER[family]);
373
397
  const chosenTier = nearestTier([...familyTiers], preferred);
374
- return (candidates.find((e) => e.tier === chosenTier) ?? candidates[0]).full;
398
+ const picked = candidates.find((e) => e.tier === chosenTier) ?? candidates[0];
399
+ return toResolved(picked.full, picked.tier);
375
400
  }
376
401
 
377
402
  // --- agy process helpers ---------------------------------------------------
@@ -671,7 +696,7 @@ export default async function (pi: ExtensionAPI) {
671
696
  ` defaultModel: ${config.defaultModel}`,
672
697
  ` defaultThinking: ${config.defaultThinking}`,
673
698
  ` permissions: ${config.skipPermissions ? "auto-approved" : "prompt"}`,
674
- ` resolved: ${resolveModel(config.defaultModel, discovered, config.defaultThinking) ?? "(agy default)"}`,
699
+ ` resolved: ${resolveModel(config.defaultModel, discovered, config.defaultThinking)?.model ?? "(agy default)"}`,
675
700
  ``,
676
701
  `Edit: ~/.pi/agent/ask-antigravity.json`,
677
702
  ].join("\n"),
@@ -682,7 +707,7 @@ export default async function (pi: ExtensionAPI) {
682
707
 
683
708
  // Resolve the display string for the current default model.
684
709
  const currentResolved =
685
- resolveModel(config.defaultModel, discovered, config.defaultThinking) ?? config.defaultModel;
710
+ resolveModel(config.defaultModel, discovered, config.defaultThinking)?.model ?? config.defaultModel;
686
711
 
687
712
  const items: SettingItem[] = [
688
713
  {
@@ -692,7 +717,7 @@ export default async function (pi: ExtensionAPI) {
692
717
  "Friendly alias resolved to the latest matching agy model. 'flash' = latest Flash, 'pro' = latest Pro, 'gemini' = latest Flash.",
693
718
  currentValue: `${config.defaultModel} → ${currentResolved}`,
694
719
  values: MODEL_OPTIONS.map((m) => {
695
- const r = resolveModel(m, discovered, config.defaultThinking) ?? m;
720
+ const r = resolveModel(m, discovered, config.defaultThinking)?.model ?? m;
696
721
  return `${m} → ${r}`;
697
722
  }),
698
723
  },
@@ -877,7 +902,9 @@ export default async function (pi: ExtensionAPI) {
877
902
  };
878
903
  }
879
904
  const resolved =
880
- resolveModel(requestedModel, discovered, config.defaultThinking) ?? requestedModel;
905
+ resolveModel(requestedModel, discovered, config.defaultThinking) ?? {
906
+ model: requestedModel,
907
+ };
881
908
 
882
909
  const start = Date.now();
883
910
  const cwd = params.cwd || ctx.cwd || process.cwd();
@@ -888,13 +915,13 @@ export default async function (pi: ExtensionAPI) {
888
915
  if (!stat.isDirectory()) {
889
916
  return {
890
917
  content: [{ type: "text", text: `cwd is not a directory: ${cwd}` }],
891
- details: emptyDetails(requestedModel, resolved),
918
+ details: emptyDetails(requestedModel, resolved.model),
892
919
  };
893
920
  }
894
921
  } catch {
895
922
  return {
896
923
  content: [{ type: "text", text: `cwd does not exist: ${cwd}` }],
897
- details: emptyDetails(requestedModel, resolved),
924
+ details: emptyDetails(requestedModel, resolved.model),
898
925
  };
899
926
  }
900
927
 
@@ -927,7 +954,8 @@ export default async function (pi: ExtensionAPI) {
927
954
  const args: string[] = ["--add-dir", cwd];
928
955
  const extra = extraArgs();
929
956
  if (extra.length) args.push(...extra);
930
- if (resolved) args.push("--model", resolved);
957
+ if (resolved.model) args.push("--model", resolved.model);
958
+ if (resolved.effort) args.push("--effort", resolved.effort);
931
959
  args.push("--mode", mode);
932
960
  // accept-edits auto-approves file edits but NOT shell commands, so a
933
961
  // run_command would hang on an unanswerable y/n prompt in non-interactive
@@ -939,7 +967,7 @@ export default async function (pi: ExtensionAPI) {
939
967
 
940
968
  const details: AgyDetails = {
941
969
  model: requestedModel,
942
- resolvedModel: resolved,
970
+ resolvedModel: resolved.model,
943
971
  mode,
944
972
  digest: useDigest,
945
973
  conversationId: isContinuation ? (rawConvId as string) : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estebanforge/pi-ask-antigravity",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
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",