@estebanforge/pi-antigravity-bridge 1.7.8 → 1.7.9

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.7.9] - 2026-10-06
6
+
7
+ ### Changed
8
+
9
+ - **Breaking: Claude models are effort-tiered now, and the ids moved to the Claude 5.5 family.** Google retired Claude 4.6 on Antigravity and ships Claude Opus 5.5 (2026-09-22) and Claude Sonnet 5.5 (2026-09-28) in its place, each with low/medium/high thinking tiers - the same shape Gemini already had. Claude joins the verified effort-capable families (probed live 2026-10: base slug + `--effort` accepted; a bare base is rejected with "requires --effort"), so `claude-opus-5-5` and `claude-sonnet-5-5` register as single toggleable models whose turns always send `--effort`, instead of per-tier fixed ids. The catalog fixtures mirror the live listing and the no-`agy models` fallback carries its head (3.8 flash, 3.1 pro, sonnet 5.5). A pattern pinned to `antigravity/claude-sonnet-4-6` (settings enabledModels, `--model`, or a scoped-model config) matches nothing after agy's upgrade and must move to `antigravity/claude-sonnet-5-5`; the bare base id itself resolves through the delegation tool to the nearest tier variant.
10
+ - **The `gpt-oss-120b-medium` family is dropped now, not next month.** Upstream announced its removal; nobody used the fixed-thinking single slug and it loses to the cheaper effort-tiered options, so the provider catalog and the delegation tool catalog both filter it before that date lands. The `gpt-oss` alias stops resolving: unrecognized input passes raw to agy and fails loudly there, instead of the tool offering a dying model.
11
+
5
12
  ## [1.7.8] - 2026-09-30
6
13
 
7
14
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estebanforge/pi-antigravity-bridge",
3
- "version": "1.7.8",
3
+ "version": "1.7.9",
4
4
  "description": "Gemini provider for Pi on the Antigravity ACP server (official Google ACP) or the stream-json agy CLI. Use antigravity/* models in Pi. Full /model picker support. Models runs Pi's tools. ToS safe to use.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -46,7 +46,7 @@
46
46
  ]
47
47
  },
48
48
  "peerDependencies": {
49
- "@earendil-works/pi-coding-agent": "*"
49
+ "@earendil-works/pi-coding-agent": "^1.0.0"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "@earendil-works/pi-coding-agent": {
@@ -25,7 +25,7 @@
25
25
  import type {
26
26
  AgentToolResult,
27
27
  AgentToolUpdateCallback,
28
- ExtensionContext,
28
+ ExtensionToolContext,
29
29
  ToolDefinition,
30
30
  } from "@earendil-works/pi-coding-agent";
31
31
 
@@ -151,7 +151,7 @@ export function createShadowTool(base: AnyToolDefinition, policy: GatePolicy, op
151
151
  params: any,
152
152
  signal: AbortSignal | undefined,
153
153
  onUpdate: AgentToolUpdateCallback<any> | undefined,
154
- ctx: ExtensionContext,
154
+ ctx: ExtensionToolContext,
155
155
  ): Promise<AgentToolResult<unknown>> => {
156
156
  const p = (params ?? {}) as Record<string, unknown>;
157
157
  if (p[GATE_MARKER] !== true) {
package/src/ask-tool.ts CHANGED
@@ -56,16 +56,19 @@ const TIER_RANK: Record<ThinkingTier, number> = { low: 0, medium: 1, high: 2 };
56
56
  // Static alias overlay for non-Gemini models agy may or may not surface.
57
57
  // Live catalog entries win on case-insensitive full-string equality; the
58
58
  // overlay resolves the alias when agy doesn't list it. Names are agy's stable
59
- // slugs (the same ids `agy models` prints and `--model` accepts).
59
+ // slugs (the same ids `agy models` prints and `--model` accepts). Claude ships
60
+ // tiered like Gemini now, so the overlay carries the full tier spread.
60
61
  const STATIC_ALIAS_OVERLAY: ReadonlyArray<ModelEntry> = [
61
- { full: "claude-sonnet-4-6", family: "other", version: null, tier: null },
62
- { full: "claude-opus-4-6-thinking", family: "other", version: null, tier: null },
63
- { full: "gpt-oss-120b-medium", family: "other", version: null, tier: null },
62
+ { full: "claude-sonnet-5-5-low", family: "other", version: null, tier: "low" },
63
+ { full: "claude-sonnet-5-5-medium", family: "other", version: null, tier: "medium" },
64
+ { full: "claude-sonnet-5-5-high", family: "other", version: null, tier: "high" },
65
+ { full: "claude-opus-5-5-low", family: "other", version: null, tier: "low" },
66
+ { full: "claude-opus-5-5-medium", family: "other", version: null, tier: "medium" },
67
+ { full: "claude-opus-5-5-high", family: "other", version: null, tier: "high" },
64
68
  ];
65
69
  const STATIC_SHORT_ALIAS: ReadonlyMap<string, string> = new Map([
66
- ["sonnet", "claude-sonnet-4-6"],
67
- ["opus", "claude-opus-4-6-thinking"],
68
- ["gpt-oss", "gpt-oss-120b-medium"],
70
+ ["sonnet", "claude-sonnet-5-5"],
71
+ ["opus", "claude-opus-5-5"],
69
72
  ]);
70
73
 
71
74
  // agy conversation ids are UUID DB-stems. First char must be alphanumeric so a
@@ -164,19 +167,47 @@ function nearestTier(available: ThinkingTier[], preferred: ThinkingTier): Thinki
164
167
  return sorted[0] ?? preferred;
165
168
  }
166
169
 
167
- /** Build the argv-facing resolution from a picked catalog entry. Gemini bases
168
- * (slugs starting "gemini-") accept a separate --effort, so split the tier
169
- * suffix out of the slug: the base alone (gemini-3.6-flash) is what --model
170
- * wants, and the tier goes to --effort. Fixed-thinking families keep agy's
171
- * exact slug even when it carries a -medium suffix (gpt-oss-120b-medium):
172
- * agy rejects --effort for them, so the suffix stays part of the slug. */
170
+ /** Build the argv-facing resolution from a picked catalog entry. Gemini and
171
+ * Claude bases (slugs starting "gemini-"/"claude-") accept a separate
172
+ * --effort, so split the tier suffix out of the slug: the base alone
173
+ * (claude-sonnet-5-5) is what --model wants, and the tier goes to --effort
174
+ * (agy rejects a bare base: "requires --effort"). Only unverified families
175
+ * keep agy's exact slug whole, so an unknown suffix can never trigger an
176
+ * unsupported --effort. */
173
177
  function toResolved(full: string, tier: ThinkingTier | null): ResolvedModel {
174
- if (tier && full.toLowerCase().startsWith("gemini-")) {
178
+ if (tier && /^(gemini|claude)-/.test(full.toLowerCase())) {
175
179
  return { model: full.replace(/-(low|medium|high)$/, ""), effort: tier };
176
180
  }
177
181
  return { model: full };
178
182
  }
179
183
 
184
+ /** Resolve a tiered base id - a short-alias target ("claude-sonnet-5-5") or a
185
+ * bare base slug the provider itself advertises - to the catalog variant
186
+ * nearest the requested tier. A bare base alone is invalid upstream
187
+ * ("requires --effort"), so the pick always rides the split in toResolved;
188
+ * with no variants in the catalog, the overlay/exact entry or the raw base
189
+ * passes through unchanged. */
190
+ function resolveTieredBase(
191
+ base: string,
192
+ entries: ModelEntry[],
193
+ defaultThinking: ThinkingTier,
194
+ preferredTier: ThinkingTier | undefined,
195
+ ): ResolvedModel {
196
+ const needle = base.toLowerCase();
197
+ const variants = entries.filter((e) => e.full.toLowerCase().startsWith(`${needle}-`));
198
+ if (variants.length > 0) {
199
+ const tiers = variants.map((e) => e.tier).filter((t): t is ThinkingTier => t !== null);
200
+ const preferred =
201
+ preferredTier ??
202
+ (tiers.includes(defaultThinking) ? defaultThinking : FAMILY_DEFAULT_TIER.other);
203
+ const chosen = nearestTier(tiers, preferred);
204
+ const picked = variants.find((e) => e.tier === chosen) ?? variants[0];
205
+ return toResolved(picked.full, picked.tier);
206
+ }
207
+ const fromCatalog = entries.find((e) => e.full.toLowerCase() === needle);
208
+ return toResolved(fromCatalog?.full ?? base, fromCatalog?.tier ?? null);
209
+ }
210
+
180
211
  /** Resolve a friendly alias / partial name to an argv-facing {model, effort?}.
181
212
  * Returns null only when the family is unrecognized; the caller then passes
182
213
  * the raw input straight to agy. */
@@ -195,9 +226,12 @@ export function resolveModel(
195
226
  if (exact) return toResolved(exact.full, exact.tier);
196
227
 
197
228
  if (STATIC_SHORT_ALIAS.has(lower)) {
198
- const target = STATIC_SHORT_ALIAS.get(lower) as string;
199
- const fromCatalog = entries.find((e) => e.full.toLowerCase() === target.toLowerCase());
200
- return toResolved(fromCatalog?.full ?? target, fromCatalog?.tier ?? null);
229
+ return resolveTieredBase(
230
+ STATIC_SHORT_ALIAS.get(lower) as string,
231
+ entries,
232
+ defaultThinking,
233
+ preferredTier,
234
+ );
201
235
  }
202
236
 
203
237
  let family: Family | null = lower.includes("flash")
@@ -213,7 +247,19 @@ export function resolveModel(
213
247
  if (!family && (/gemini/.test(lower) || lower === "" || lower === "default")) {
214
248
  family = "flash";
215
249
  }
216
- if (!family) return null;
250
+ if (!family) {
251
+ // A bare base slug the provider itself advertises ("claude-sonnet-5-5",
252
+ // the id behind the antigravity/ entry in pi's picker) is invalid
253
+ // upstream without an effort: resolve it to the nearest tier variant
254
+ // exactly like the short aliases.
255
+ if (
256
+ entries.some((e) => e.full.toLowerCase().startsWith(`${lower}-`)) ||
257
+ entries.some((e) => e.full.toLowerCase() === lower)
258
+ ) {
259
+ return resolveTieredBase(lower, entries, defaultThinking, preferredTier);
260
+ }
261
+ return null;
262
+ }
217
263
 
218
264
  let candidates = entries.filter((e) => e.family === family);
219
265
  if (candidates.length === 0) return null;
@@ -252,11 +298,18 @@ export function resolveModel(
252
298
  return toResolved(picked.full, picked.tier);
253
299
  }
254
300
 
301
+ /** Families dropped outright even while `agy models` still lists them (same
302
+ * policy as models.ts HIDDEN_FAMILY_RE): never offered, never resolved. */
303
+ const HIDDEN_FAMILY_RE = /^gpt-oss-/;
304
+
255
305
  /** Parse raw `agy models` text into tool-catalog entries (all families, plus
256
- * the static sonnet/opus/gpt-oss overlay). Pure: no spawn. */
306
+ * the static sonnet/opus overlay). Pure: no spawn. */
257
307
  export function toolModelsFromRaw(raw: string): ModelEntry[] {
258
308
  return mergeCatalog(
259
- raw.split("\n").map(parseModelLine).filter((e): e is ModelEntry => e !== null),
309
+ raw
310
+ .split("\n")
311
+ .map(parseModelLine)
312
+ .filter((e): e is ModelEntry => e !== null && !HIDDEN_FAMILY_RE.test(e.full)),
260
313
  );
261
314
  }
262
315
 
package/src/models.ts CHANGED
@@ -2,20 +2,21 @@
2
2
  // they appear in the /model picker as antigravity/<slug>.
3
3
  //
4
4
  // agy prints TWO columns per line: "<slug> <display label>". --model takes
5
- // ONLY the slug (col 1); the label is display-only. Verified live, e.g.:
6
- // gemini-3.6-flash-high Gemini 3.6 Flash (High)
7
- // gemini-3.6-flash-medium Gemini 3.6 Flash (Medium) (+ -low)
5
+ // ONLY the slug (col 1); the label is display-only. Verified live 2026-10:
6
+ // gemini-3.8-flash-high Gemini 3.8 Flash (High)
7
+ // gemini-3.8-flash-medium Gemini 3.8 Flash (Medium) (+ -low)
8
8
  // gemini-3.1-pro-high Gemini 3.1 Pro (High) (Pro has NO medium)
9
- // claude-sonnet-4-6 Claude Sonnet 4.6 (Thinking) (fixed, no tiers)
10
- // gpt-oss-120b-medium GPT-OSS 120B (Medium) (fixed, no tiers)
9
+ // claude-sonnet-5-5-high Claude Sonnet 5.5 (High) (+ -medium, -low)
11
10
  //
12
- // Gemini models are collapsed to a BASE slug (gemini-3.6-flash) and exposed
13
- // with a thinking-effort toggle whose levels match exactly the tiers agy
14
- // offers that base (verified: Pro rejects medium). The picked level is sent as
15
- // agy --effort; a base slug is INVALID on its own, so effort is always passed.
16
- // Claude and GPT-OSS keep agy's exact slug with no toggle: their thinking is
17
- // fixed and agy rejects --effort for them. Google's Antigravity subscription
18
- // bills all of these through agy.
11
+ // Gemini AND Claude models are collapsed to a BASE slug (gemini-3.8-flash,
12
+ // claude-sonnet-5-5) and exposed with a thinking-effort toggle whose levels
13
+ // match exactly the tiers agy offers that base (verified: Pro rejects medium;
14
+ // Claude 5.5 accepts low/medium/high). The picked level is sent as agy
15
+ // --effort; a bare base slug is INVALID on its own (agy: "requires --effort"),
16
+ // so effort is always passed. Only unverified families keep agy's exact slug
17
+ // with no toggle, so an unknown future family degrades safely; the retired
18
+ // gpt-oss family is dropped outright (HIDDEN_FAMILY_RE). Google's Antigravity
19
+ // subscription bills all of these through agy.
19
20
 
20
21
  import { spawn } from "node:child_process";
21
22
  import fs from "node:fs";
@@ -81,8 +82,9 @@ export function toAgyEffort(
81
82
  }
82
83
 
83
84
  /** Split an agy slug into (base, tier). tier is null when the slug has no
84
- * -high/-medium/-low suffix (claude-sonnet-4-6, gpt-oss-120b-medium's "medium"
85
- * IS its suffix here, claude-opus-4-6-thinking is not a tier). */
85
+ * -high/-medium/-low suffix - a shape only unverified families should
86
+ * produce now, and those stay whole. Every Claude slug carries a tier
87
+ * suffix and collapses to its base. */
86
88
  const TIER_RE = /^(.+)-(high|medium|low)$/;
87
89
 
88
90
  /** agy emits clean slug ids (gemini-3.6-flash-high). Validate col1 of each
@@ -97,14 +99,21 @@ const MODEL_LINE_RE = /^[A-Za-z0-9][A-Za-z0-9._]*-[A-Za-z0-9._-]*$/;
97
99
  * to a base slug with a thinking toggle. Any other family stays as agy's exact
98
100
  * qualified slug (always valid on its own), so an unknown or fixed-thinking
99
101
  * family degrades safely instead of forcing an unsupported --effort. Add a
100
- * family here only after confirming base+--effort is accepted for it. */
101
- const EFFORT_CAPABLE_FAMILIES: readonly RegExp[] = [/^gemini-/];
102
+ * family here only after confirming base+--effort is accepted for it.
103
+ * Claude 5.5 verified live 2026-10: base+--effort accepted; a bare base is
104
+ * rejected ("requires --effort"). */
105
+ const EFFORT_CAPABLE_FAMILIES: readonly RegExp[] = [/^gemini-/, /^claude-/];
106
+
107
+ /** Families dropped outright, even while `agy models` still lists them: the
108
+ * fixed-thinking single slug loses to the cheaper effort-tiered options and
109
+ * its removal upstream is already announced, so we stop offering it now. */
110
+ const HIDDEN_FAMILY_RE = /^gpt-oss-/;
102
111
 
103
112
  export interface AgyModelEntry {
104
- /** Exact agy --model string to pass: a base slug ("gemini-3.6-flash") when
105
- * effort-driven, else agy's full qualified slug ("claude-opus-4-6-thinking"). */
113
+ /** Exact agy --model string to pass: a base slug ("gemini-3.8-flash") when
114
+ * effort-driven, else agy's full qualified slug (unverified family). */
106
115
  full: string;
107
- /** pi model id, e.g. "gemini-3-6-flash". */
116
+ /** pi model id, e.g. "gemini-3-8-flash". */
108
117
  id: string;
109
118
  /** Present iff this is an effort-driven base slug. Lists the tiers agy
110
119
  * accepts for it; pi's thinking toggle picks among them and we always pass
@@ -284,11 +293,12 @@ export async function loadModelCatalogRaw(
284
293
 
285
294
  /** Parse raw `agy models` text into the provider's model entries.
286
295
  *
287
- * Effort-driven Gemini bases (>= 2 tier variants in the catalog) collapse to
288
- * one BASE-slug entry carrying the tiers they accept. Models with 0 or 1 tier
289
- * variants keep agy's exact qualified slug: a single suffix (gpt-oss-120b-
290
- * medium) or none (claude-sonnet-4-6) means fixed thinking, where --effort is
291
- * unsupported. Insertion order of first-seen bases is preserved. */
296
+ * Effort-driven bases (>= 2 tier variants in the catalog, verified
297
+ * effort-capable family) collapse to one BASE-slug entry carrying the tiers
298
+ * they accept. Models with 0 or 1 tier variants keep agy's exact qualified
299
+ * slug: without a verified effort-capable family match, --effort is never
300
+ * risked. Hidden families are dropped outright. Insertion order of
301
+ * first-seen bases is preserved. */
292
302
  export function entriesFromRaw(raw: string): AgyModelEntry[] {
293
303
  // agy prints TWO columns: "<slug> <display label>". --model takes only the
294
304
  // slug, so split col1 and validate THAT; the label is display-only. A
@@ -298,6 +308,7 @@ export function entriesFromRaw(raw: string): AgyModelEntry[] {
298
308
  for (const line of raw.split("\n")) {
299
309
  const slug = line.trim().split(/\s+/)[0] ?? "";
300
310
  if (!slug || !MODEL_LINE_RE.test(slug)) continue;
311
+ if (HIDDEN_FAMILY_RE.test(slug)) continue;
301
312
  const m = TIER_RE.exec(slug);
302
313
  const base = m ? (m[1] as string) : slug;
303
314
  const tier = m ? (m[2] as AgyEffort) : null;
@@ -365,8 +376,8 @@ export function toPiModel(entry: AgyModelEntry, input: Array<"text" | "image"> =
365
376
  // sentinel that no built-in provider claims, so it can never collide.
366
377
  baseUrl: "agy-bridge://antigravity",
367
378
  // reasoning=true only for effort-driven bases => pi shows the toggle.
368
- // Fixed models (Claude/GPT-OSS) get no toggle: their thinking can't be
369
- // changed and agy rejects --effort for them.
379
+ // Unverified families get no toggle: --effort support is unknown, so
380
+ // none is ever sent.
370
381
  reasoning: effortDriven,
371
382
  ...(effortDriven ? { thinkingLevelMap: thinkingLevelMapFor(entry.efforts!) } : {}),
372
383
  // Input advertising comes from the caller (engine-dependent): the ACP
@@ -386,9 +397,9 @@ export function toPiModel(entry: AgyModelEntry, input: Array<"text" | "image"> =
386
397
  * can still select a model and get a clear runtime error instead of an empty
387
398
  * list. Update these when agy ships new Gemini versions. */
388
399
  export const FALLBACK_MODELS: AgyModelEntry[] = [
389
- { full: "gemini-3.7-flash", id: "gemini-3-7-flash", efforts: ["low", "medium", "high"] },
400
+ { full: "gemini-3.8-flash", id: "gemini-3-8-flash", efforts: ["low", "medium", "high"] },
390
401
  { full: "gemini-3.1-pro", id: "gemini-3-1-pro", efforts: ["low", "high"] },
391
- { full: "claude-sonnet-4-6", id: "claude-sonnet-4-6" },
402
+ { full: "claude-sonnet-5-5", id: "claude-sonnet-5-5", efforts: ["low", "medium", "high"] },
392
403
  ];
393
404
 
394
405