@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 +7 -0
- package/package.json +2 -2
- package/src/approval-gate.ts +2 -2
- package/src/ask-tool.ts +73 -20
- package/src/models.ts +39 -28
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.
|
|
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": {
|
package/src/approval-gate.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import type {
|
|
26
26
|
AgentToolResult,
|
|
27
27
|
AgentToolUpdateCallback,
|
|
28
|
-
|
|
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:
|
|
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-
|
|
62
|
-
{ full: "claude-
|
|
63
|
-
{ full: "
|
|
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-
|
|
67
|
-
["opus", "claude-opus-
|
|
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
|
|
168
|
-
* (slugs starting "gemini-") accept a separate
|
|
169
|
-
* suffix out of the slug: the base alone
|
|
170
|
-
* wants, and the tier goes to --effort
|
|
171
|
-
*
|
|
172
|
-
* agy
|
|
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()
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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)
|
|
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
|
|
306
|
+
* the static sonnet/opus overlay). Pure: no spawn. */
|
|
257
307
|
export function toolModelsFromRaw(raw: string): ModelEntry[] {
|
|
258
308
|
return mergeCatalog(
|
|
259
|
-
raw
|
|
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
|
|
6
|
-
// gemini-3.
|
|
7
|
-
// gemini-3.
|
|
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-
|
|
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.
|
|
13
|
-
// with a thinking-effort toggle whose levels
|
|
14
|
-
// offers that base (verified: Pro rejects medium
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
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
|
|
85
|
-
*
|
|
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
|
-
|
|
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.
|
|
105
|
-
* effort-driven, else agy's full qualified slug (
|
|
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-
|
|
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
|
|
288
|
-
* one BASE-slug entry carrying the tiers
|
|
289
|
-
* variants keep agy's exact qualified
|
|
290
|
-
*
|
|
291
|
-
*
|
|
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
|
-
//
|
|
369
|
-
//
|
|
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.
|
|
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-
|
|
402
|
+
{ full: "claude-sonnet-5-5", id: "claude-sonnet-5-5", efforts: ["low", "medium", "high"] },
|
|
392
403
|
];
|
|
393
404
|
|
|
394
405
|
|