@estebanforge/pi-antigravity-bridge 1.1.2 → 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.
- package/CHANGELOG.md +17 -0
- package/package.json +1 -1
- package/src/ask-tool.ts +43 -12
- package/src/models.ts +24 -19
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.2.3] - 2026-08-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Model parsing now handles agy's real two-column output.** `agy models`
|
|
10
|
+
prints `<slug> <display label>` per line, and `--model` accepts only the
|
|
11
|
+
slug. `entriesFromRaw` (provider path) applied its slug regex to the whole
|
|
12
|
+
line, so every real line was rejected and the provider always fell back to
|
|
13
|
+
the hardcoded catalog; it now splits column 1 and requires a hyphen, which
|
|
14
|
+
also drops banner words split out of column 1. The AskAntigravity resolver
|
|
15
|
+
swallowed slug + label into `--model`, which agy rejected; it now returns a
|
|
16
|
+
`{model, effort?}` shape that sends Gemini-family bases' base slug to
|
|
17
|
+
`--model` and their tier to `--effort`, while fixed-thinking families
|
|
18
|
+
(Claude, GPT-OSS) keep the exact slug with no `--effort` (agy rejects it for
|
|
19
|
+
them). Matches the provider's collapse + clamping path. Tests rewritten to
|
|
20
|
+
the verified live `agy models` fixture.
|
|
21
|
+
|
|
5
22
|
## [1.1.2] - 2026-08-10
|
|
6
23
|
|
|
7
24
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estebanforge/pi-antigravity-bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Streaming Gemini provider for pi, built on the agy CLI. Registers antigravity/* models in pi's /model picker via SQLite polling + protobuf decode of agy's conversation DBs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/ask-tool.ts
CHANGED
|
@@ -84,6 +84,15 @@ interface ModelEntry {
|
|
|
84
84
|
tier: ThinkingTier | null;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/** Argv-facing model resolution: the exact --model slug plus an optional
|
|
88
|
+
* --effort tier. Gemini bases split the tier out (the base slug alone is
|
|
89
|
+
* invalid without --effort); fixed-thinking families keep agy's exact slug
|
|
90
|
+
* and carry no effort. */
|
|
91
|
+
interface ResolvedModel {
|
|
92
|
+
model: string;
|
|
93
|
+
effort?: ThinkingTier;
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
// --- Version helpers -------------------------------------------------------
|
|
88
97
|
|
|
89
98
|
/** Descending numeric version compare (3.10 > 3.9, not lexical). */
|
|
@@ -110,7 +119,10 @@ function mergeCatalog(live: ModelEntry[]): ModelEntry[] {
|
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
function parseModelLine(line: string): ModelEntry | null {
|
|
113
|
-
|
|
122
|
+
// agy prints TWO columns: "<slug> <display label>". --model takes only the
|
|
123
|
+
// slug (col 1), so split it off; the label is display-only and must never
|
|
124
|
+
// reach --model. A bare-slug line (no whitespace) splits to itself.
|
|
125
|
+
const full = line.trim().split(/\s+/)[0] ?? "";
|
|
114
126
|
if (!full) return null;
|
|
115
127
|
const lower = full.toLowerCase();
|
|
116
128
|
const family: Family = lower.includes("flash")
|
|
@@ -135,21 +147,36 @@ function nearestTier(available: ThinkingTier[], preferred: ThinkingTier): Thinki
|
|
|
135
147
|
return sorted[0] ?? preferred;
|
|
136
148
|
}
|
|
137
149
|
|
|
138
|
-
/**
|
|
150
|
+
/** Build the argv-facing resolution from a picked catalog entry. Gemini bases
|
|
151
|
+
* (slugs starting "gemini-") accept a separate --effort, so split the tier
|
|
152
|
+
* suffix out of the slug: the base alone (gemini-3.6-flash) is what --model
|
|
153
|
+
* wants, and the tier goes to --effort. Fixed-thinking families keep agy's
|
|
154
|
+
* exact slug even when it carries a -medium suffix (gpt-oss-120b-medium):
|
|
155
|
+
* agy rejects --effort for them, so the suffix stays part of the slug. */
|
|
156
|
+
function toResolved(full: string, tier: ThinkingTier | null): ResolvedModel {
|
|
157
|
+
if (tier && full.toLowerCase().startsWith("gemini-")) {
|
|
158
|
+
return { model: full.replace(/-(low|medium|high)$/, ""), effort: tier };
|
|
159
|
+
}
|
|
160
|
+
return { model: full };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Resolve a friendly alias / partial name to an argv-facing {model, effort?}.
|
|
164
|
+
* Returns null only when the family is unrecognized; the caller then passes
|
|
165
|
+
* the raw input straight to agy. */
|
|
139
166
|
export function resolveModel(
|
|
140
167
|
input: string,
|
|
141
168
|
entries: ModelEntry[],
|
|
142
169
|
defaultThinking: ThinkingTier,
|
|
143
|
-
):
|
|
170
|
+
): ResolvedModel | null {
|
|
144
171
|
const lower = input.toLowerCase().trim();
|
|
145
172
|
|
|
146
173
|
const exact = entries.find((e) => e.full.toLowerCase() === lower);
|
|
147
|
-
if (exact) return exact.full;
|
|
174
|
+
if (exact) return toResolved(exact.full, exact.tier);
|
|
148
175
|
|
|
149
176
|
if (STATIC_SHORT_ALIAS.has(lower)) {
|
|
150
177
|
const target = STATIC_SHORT_ALIAS.get(lower) as string;
|
|
151
178
|
const fromCatalog = entries.find((e) => e.full.toLowerCase() === target.toLowerCase());
|
|
152
|
-
return fromCatalog
|
|
179
|
+
return toResolved(fromCatalog?.full ?? target, fromCatalog?.tier ?? null);
|
|
153
180
|
}
|
|
154
181
|
|
|
155
182
|
let family: Family | null = lower.includes("flash")
|
|
@@ -193,13 +220,14 @@ export function resolveModel(
|
|
|
193
220
|
const familyTiers = new Set(
|
|
194
221
|
candidates.map((e) => e.tier).filter((t): t is ThinkingTier => t !== null),
|
|
195
222
|
);
|
|
196
|
-
if (familyTiers.size === 0) return candidates[0].full;
|
|
223
|
+
if (familyTiers.size === 0) return toResolved(candidates[0].full, null);
|
|
197
224
|
|
|
198
225
|
const preferred =
|
|
199
226
|
tier ??
|
|
200
227
|
(familyTiers.has(defaultThinking) ? defaultThinking : FAMILY_DEFAULT_TIER[family]);
|
|
201
228
|
const chosenTier = nearestTier([...familyTiers], preferred);
|
|
202
|
-
|
|
229
|
+
const picked = candidates.find((e) => e.tier === chosenTier) ?? candidates[0];
|
|
230
|
+
return toResolved(picked.full, picked.tier);
|
|
203
231
|
}
|
|
204
232
|
|
|
205
233
|
/** Parse raw `agy models` text into tool-catalog entries (all families, plus
|
|
@@ -304,7 +332,9 @@ export async function registerAskAntigravityTool(
|
|
|
304
332
|
};
|
|
305
333
|
}
|
|
306
334
|
const resolved =
|
|
307
|
-
resolveModel(requestedModel, entries, config.defaultThinking) ??
|
|
335
|
+
resolveModel(requestedModel, entries, config.defaultThinking) ?? {
|
|
336
|
+
model: requestedModel,
|
|
337
|
+
};
|
|
308
338
|
|
|
309
339
|
const start = Date.now();
|
|
310
340
|
const cwd = params.cwd || ctx.cwd || process.cwd();
|
|
@@ -313,13 +343,13 @@ export async function registerAskAntigravityTool(
|
|
|
313
343
|
if (!stat.isDirectory()) {
|
|
314
344
|
return {
|
|
315
345
|
content: [{ type: "text", text: `cwd is not a directory: ${cwd}` }],
|
|
316
|
-
details: emptyDetails(requestedModel, resolved),
|
|
346
|
+
details: emptyDetails(requestedModel, resolved.model),
|
|
317
347
|
};
|
|
318
348
|
}
|
|
319
349
|
} catch {
|
|
320
350
|
return {
|
|
321
351
|
content: [{ type: "text", text: `cwd does not exist: ${cwd}` }],
|
|
322
|
-
details: emptyDetails(requestedModel, resolved),
|
|
352
|
+
details: emptyDetails(requestedModel, resolved.model),
|
|
323
353
|
};
|
|
324
354
|
}
|
|
325
355
|
|
|
@@ -340,7 +370,8 @@ export async function registerAskAntigravityTool(
|
|
|
340
370
|
const args: string[] = ["--add-dir", cwd];
|
|
341
371
|
const extra = extraArgs();
|
|
342
372
|
if (extra.length) args.push(...extra);
|
|
343
|
-
if (resolved) args.push("--model", resolved);
|
|
373
|
+
if (resolved.model) args.push("--model", resolved.model);
|
|
374
|
+
if (resolved.effort) args.push("--effort", resolved.effort);
|
|
344
375
|
args.push("--mode", mode);
|
|
345
376
|
// Honor the shared permissions setting (same knob as the provider). Non-
|
|
346
377
|
// interactive -p can't answer a permission prompt, so when this is off
|
|
@@ -352,7 +383,7 @@ export async function registerAskAntigravityTool(
|
|
|
352
383
|
|
|
353
384
|
const details: AgyDetails = {
|
|
354
385
|
model: requestedModel,
|
|
355
|
-
resolvedModel: resolved,
|
|
386
|
+
resolvedModel: resolved.model,
|
|
356
387
|
mode,
|
|
357
388
|
digest: useDigest,
|
|
358
389
|
conversationId: isContinuation ? (rawConvId as string) : null,
|
package/src/models.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// Discover models from `agy models` and project them into pi's Model shape so
|
|
2
2
|
// they appear in the /model picker as antigravity/<slug>.
|
|
3
3
|
//
|
|
4
|
-
// agy
|
|
5
|
-
//
|
|
6
|
-
// gemini-3.
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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)
|
|
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
11
|
//
|
|
10
12
|
// Gemini models are collapsed to a BASE slug (gemini-3.6-flash) and exposed
|
|
11
13
|
// with a thinking-effort toggle whose levels match exactly the tiers agy
|
|
@@ -34,11 +36,13 @@ const EFFORT_RANK: Record<AgyEffort, number> = { low: 0, medium: 1, high: 2 };
|
|
|
34
36
|
* IS its suffix here, claude-opus-4-6-thinking is not a tier). */
|
|
35
37
|
const TIER_RE = /^(.+)-(high|medium|low)$/;
|
|
36
38
|
|
|
37
|
-
/** agy emits clean slug ids (gemini-3.6-flash-high).
|
|
38
|
-
* banner / auth / "Fetching models…" line can't register as a
|
|
39
|
-
* leading-dash token (e.g. "-high") can't reach agy's flag
|
|
40
|
-
* --model. First char must be alphanumeric
|
|
41
|
-
|
|
39
|
+
/** agy emits clean slug ids (gemini-3.6-flash-high). Validate col1 of each
|
|
40
|
+
* line so a banner / auth / "Fetching models…" line can't register as a
|
|
41
|
+
* model, and a leading-dash token (e.g. "-high") can't reach agy's flag
|
|
42
|
+
* parser as --model. First char must be alphanumeric; the slug must contain
|
|
43
|
+
* at least one hyphen (every real agy slug does: family-version-name), which
|
|
44
|
+
* also drops a prose banner word split out of col1 ("Available"). */
|
|
45
|
+
const MODEL_LINE_RE = /^[A-Za-z0-9][A-Za-z0-9._]*-[A-Za-z0-9._-]*$/;
|
|
42
46
|
|
|
43
47
|
/** Model families VERIFIED to accept base-slug + --effort. Only these collapse
|
|
44
48
|
* to a base slug with a thinking toggle. Any other family stays as agy's exact
|
|
@@ -210,22 +214,23 @@ export async function loadModelCatalogRaw(
|
|
|
210
214
|
* medium) or none (claude-sonnet-4-6) means fixed thinking, where --effort is
|
|
211
215
|
* unsupported. Insertion order of first-seen bases is preserved. */
|
|
212
216
|
export function entriesFromRaw(raw: string): AgyModelEntry[] {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
.filter((line) => MODEL_LINE_RE.test(line));
|
|
217
|
+
// agy prints TWO columns: "<slug> <display label>". --model takes only the
|
|
218
|
+
// slug, so split col1 and validate THAT; the label is display-only. A
|
|
219
|
+
// bare-slug line (no whitespace) splits to itself, so this also tolerates
|
|
220
|
+
// the legacy one-column shape.
|
|
218
221
|
const groups = new Map<string, { lines: string[]; tiers: Set<AgyEffort> }>();
|
|
219
|
-
for (const line of
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
+
for (const line of raw.split("\n")) {
|
|
223
|
+
const slug = line.trim().split(/\s+/)[0] ?? "";
|
|
224
|
+
if (!slug || !MODEL_LINE_RE.test(slug)) continue;
|
|
225
|
+
const m = TIER_RE.exec(slug);
|
|
226
|
+
const base = m ? (m[1] as string) : slug;
|
|
222
227
|
const tier = m ? (m[2] as AgyEffort) : null;
|
|
223
228
|
let g = groups.get(base);
|
|
224
229
|
if (!g) {
|
|
225
230
|
g = { lines: [], tiers: new Set<AgyEffort>() };
|
|
226
231
|
groups.set(base, g);
|
|
227
232
|
}
|
|
228
|
-
g.lines.push(
|
|
233
|
+
g.lines.push(slug);
|
|
229
234
|
if (tier) g.tiers.add(tier);
|
|
230
235
|
}
|
|
231
236
|
const entries: AgyModelEntry[] = [];
|