@estebanforge/pi-antigravity-bridge 1.6.0 → 1.6.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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/ask-tool.ts +52 -5
- package/src/models.ts +42 -1
- package/src/provider.ts +1 -41
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.6.1] - 2026-09-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`thinking` / `effort` params on AskAntigravity (synonyms).** Delegation calls had no direct tier knob: the only lever was tier sugar inside `model` ("flash high"), so a caller told "peer review on high thinking" fell through to the configured default (medium) with no signal anything was dropped. The tool now takes `thinking` (pi vocabulary: minimal|low|medium|high|xhigh|max, clamped to agy's low|medium|high; unknown values fall back to low) plus `effort` as the same knob under agy's own name, so calling models reach for either word. An explicit level beats a tier embedded in `model` and the config default; passing both with different values errors; fixed-thinking families (Claude, GPT-OSS) ignore it because agy rejects `--effort` for them. `toAgyEffort` moved from `src/provider.ts` to `src/models.ts` so provider turns and the delegation tool share one clamp.
|
|
10
|
+
|
|
5
11
|
## [1.6.0] - 2026-09-14
|
|
6
12
|
|
|
7
13
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estebanforge/pi-antigravity-bridge",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Gemini provider for Pi on the Antigravity ACP server (official Google ACP) or the stream-json agy CLI. antigravity/* models in Pi's /model picker, no-patch MCP bridge: agy runs Pi's tools. ToS safe to use.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/ask-tool.ts
CHANGED
|
@@ -17,7 +17,7 @@ import * as path from "node:path";
|
|
|
17
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import { buildSessionContext, getAgentDir, keyHint } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import { Text } from "@earendil-works/pi-tui";
|
|
20
|
-
import { contentText } from "@earendil-works/pi-ai";
|
|
20
|
+
import { contentText, type ThinkingLevel } from "@earendil-works/pi-ai";
|
|
21
21
|
import { Type } from "typebox";
|
|
22
22
|
import {
|
|
23
23
|
CONVERSATIONS_DIR,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "./discovery.js";
|
|
27
27
|
import { loadConfig, type AgyMode, type ThinkingTier } from "./config.js";
|
|
28
28
|
import { acquireBridgeSuppression } from "./mcp-registration.js";
|
|
29
|
-
import { spawnAgyModelsRaw } from "./models.js";
|
|
29
|
+
import { AGY_EFFORT_ORDER, spawnAgyModelsRaw, toAgyEffort } from "./models.js";
|
|
30
30
|
|
|
31
31
|
// --- Constants -------------------------------------------------------------
|
|
32
32
|
|
|
@@ -80,7 +80,12 @@ EXECUTION MODES (param: mode):
|
|
|
80
80
|
- **plan**: agy reviews and plans without writing. Use for cross-review and read-only tasks.
|
|
81
81
|
- **accept-edits** (default): agy applies edits directly inside the workspace.
|
|
82
82
|
|
|
83
|
-
COMPACT OUTPUT (param: digest): when true, the prompt is prefixed to request compact digests instead of full file contents. Defaults on for plan, off for accept-edits
|
|
83
|
+
COMPACT OUTPUT (param: digest): when true, the prompt is prefixed to request compact digests instead of full file contents. Defaults on for plan, off for accept-edits.
|
|
84
|
+
|
|
85
|
+
THINKING LEVEL (params: thinking, effort - SYNONYMS for one knob):
|
|
86
|
+
- pi calls it thinking, agy calls it effort. Same thing. Pass ONE of the two.
|
|
87
|
+
- Values (pi vocabulary): minimal|low|medium|high|xhigh|max. Clamped to agy's low|medium|high; unknown values fall back to low. "peer review on high thinking" -> thinking: "high".
|
|
88
|
+
- An explicit level beats a tier embedded in model ("flash high") and the configured default. Omit both for the configured default.`;
|
|
84
89
|
|
|
85
90
|
// --- Types -----------------------------------------------------------------
|
|
86
91
|
|
|
@@ -176,6 +181,10 @@ export function resolveModel(
|
|
|
176
181
|
input: string,
|
|
177
182
|
entries: ModelEntry[],
|
|
178
183
|
defaultThinking: ThinkingTier,
|
|
184
|
+
/** Explicit thinking param (thinking/effort). Beats a tier embedded in
|
|
185
|
+
* the alias ("flash high") and the configured default; clamped to the
|
|
186
|
+
* family's real tiers, ignored for fixed-thinking families. */
|
|
187
|
+
preferredTier?: ThinkingTier,
|
|
179
188
|
): ResolvedModel | null {
|
|
180
189
|
const lower = input.toLowerCase().trim();
|
|
181
190
|
|
|
@@ -232,6 +241,7 @@ export function resolveModel(
|
|
|
232
241
|
if (familyTiers.size === 0) return toResolved(candidates[0].full, null);
|
|
233
242
|
|
|
234
243
|
const preferred =
|
|
244
|
+
preferredTier ??
|
|
235
245
|
tier ??
|
|
236
246
|
(familyTiers.has(defaultThinking) ? defaultThinking : FAMILY_DEFAULT_TIER[family]);
|
|
237
247
|
const chosenTier = nearestTier([...familyTiers], preferred);
|
|
@@ -293,6 +303,18 @@ export async function registerAskAntigravityTool(
|
|
|
293
303
|
"Model alias or exact id. Friendly: 'flash', 'pro', 'gemini'. Add a tier: 'flash high'. Pin a version: '3.5 flash'. Exact: 'gemini-3.6-flash-medium'. Omit for the configured default.",
|
|
294
304
|
}),
|
|
295
305
|
),
|
|
306
|
+
thinking: Type.Optional(
|
|
307
|
+
Type.String({
|
|
308
|
+
description:
|
|
309
|
+
"Thinking level (= agy effort tier). pi vocabulary: minimal|low|medium|high|xhigh|max, clamped to agy's low|medium|high (unknown values fall back to low). Overrides a tier embedded in `model`. Omit for the configured default.",
|
|
310
|
+
}),
|
|
311
|
+
),
|
|
312
|
+
effort: Type.Optional(
|
|
313
|
+
Type.String({
|
|
314
|
+
description:
|
|
315
|
+
"Alias for `thinking` (agy's own name for the same knob). Pass ONE of the two; different values on both is an error.",
|
|
316
|
+
}),
|
|
317
|
+
),
|
|
296
318
|
mode: Type.Optional(
|
|
297
319
|
Type.Union([Type.Literal("plan"), Type.Literal("accept-edits")], {
|
|
298
320
|
description:
|
|
@@ -327,8 +349,14 @@ export async function registerAskAntigravityTool(
|
|
|
327
349
|
// row identifies what will actually run, not just explicit args.
|
|
328
350
|
const cfg = loadConfig();
|
|
329
351
|
const requestedModel = (args.model as string | undefined)?.trim() || cfg.defaultModel;
|
|
352
|
+
const thinkingArg = (args.thinking as string | undefined) ?? (args.effort as string | undefined);
|
|
330
353
|
const resolved =
|
|
331
|
-
resolveModel(
|
|
354
|
+
resolveModel(
|
|
355
|
+
requestedModel,
|
|
356
|
+
entries,
|
|
357
|
+
cfg.defaultThinking,
|
|
358
|
+
thinkingArg ? toAgyEffort(thinkingArg as ThinkingLevel, AGY_EFFORT_ORDER) : undefined,
|
|
359
|
+
) ?? { model: requestedModel };
|
|
332
360
|
const thinking: ThinkingTier = resolved.effort ?? cfg.defaultThinking;
|
|
333
361
|
const mode: AgyMode = (args.mode as AgyMode | undefined) ?? "accept-edits";
|
|
334
362
|
const useDigest = typeof args.digest === "boolean" ? args.digest : mode === "plan";
|
|
@@ -415,8 +443,27 @@ export async function registerAskAntigravityTool(
|
|
|
415
443
|
details: emptyDetails(requestedModel),
|
|
416
444
|
};
|
|
417
445
|
}
|
|
446
|
+
if (
|
|
447
|
+
typeof params.thinking === "string" &&
|
|
448
|
+
typeof params.effort === "string" &&
|
|
449
|
+
params.thinking !== params.effort
|
|
450
|
+
) {
|
|
451
|
+
return {
|
|
452
|
+
content: [
|
|
453
|
+
{
|
|
454
|
+
type: "text",
|
|
455
|
+
text: "thinking and effort are synonyms for the same knob - pass one, not both with different values.",
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
details: emptyDetails(requestedModel),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const thinkingArg = (params.thinking as string | undefined) ?? (params.effort as string | undefined);
|
|
462
|
+
const preferredTier = thinkingArg
|
|
463
|
+
? toAgyEffort(thinkingArg as ThinkingLevel, AGY_EFFORT_ORDER)
|
|
464
|
+
: undefined;
|
|
418
465
|
const resolved =
|
|
419
|
-
resolveModel(requestedModel, entries, config.defaultThinking) ?? {
|
|
466
|
+
resolveModel(requestedModel, entries, config.defaultThinking, preferredTier) ?? {
|
|
420
467
|
model: requestedModel,
|
|
421
468
|
};
|
|
422
469
|
|
package/src/models.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { spawn } from "node:child_process";
|
|
|
21
21
|
import fs from "node:fs";
|
|
22
22
|
import os from "node:os";
|
|
23
23
|
import path from "node:path";
|
|
24
|
-
import type { Api, Model, ThinkingLevelMap } from "@earendil-works/pi-ai";
|
|
24
|
+
import type { Api, Model, ThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai";
|
|
25
25
|
|
|
26
26
|
const DISCOVERY_TIMEOUT_MS = 8_000;
|
|
27
27
|
|
|
@@ -31,6 +31,47 @@ export type AgyEffort = "low" | "medium" | "high";
|
|
|
31
31
|
/** low < medium < high, for sorting/clamping. */
|
|
32
32
|
const EFFORT_RANK: Record<AgyEffort, number> = { low: 0, medium: 1, high: 2 };
|
|
33
33
|
|
|
34
|
+
/** pi thinking-effort order mirrors agy's, for clamping. */
|
|
35
|
+
export const AGY_EFFORT_ORDER: readonly AgyEffort[] = ["low", "medium", "high"];
|
|
36
|
+
|
|
37
|
+
/** Map pi's thinking level onto one of the tiers `efforts` the base actually
|
|
38
|
+
* supports, clamping to the nearest available. agy rejects an effort tier a
|
|
39
|
+
* base doesn't list (e.g. medium on Pro), so we never emit one. Shared by the
|
|
40
|
+
* provider turns AND the AskAntigravity delegation tool (thinking/effort
|
|
41
|
+
* params): one vocabulary, one clamp. When pi sends no level we fall to the
|
|
42
|
+
* first available tier. */
|
|
43
|
+
export function toAgyEffort(
|
|
44
|
+
reasoning: ThinkingLevel | undefined,
|
|
45
|
+
efforts: readonly AgyEffort[],
|
|
46
|
+
): AgyEffort {
|
|
47
|
+
let candidate: AgyEffort;
|
|
48
|
+
switch (reasoning) {
|
|
49
|
+
case "minimal":
|
|
50
|
+
case "low":
|
|
51
|
+
candidate = "low";
|
|
52
|
+
break;
|
|
53
|
+
case "medium":
|
|
54
|
+
candidate = "medium";
|
|
55
|
+
break;
|
|
56
|
+
case "high":
|
|
57
|
+
case "xhigh":
|
|
58
|
+
case "max":
|
|
59
|
+
candidate = "high";
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
candidate = efforts[0] ?? "low";
|
|
63
|
+
}
|
|
64
|
+
if (efforts.includes(candidate)) return candidate;
|
|
65
|
+
const i = AGY_EFFORT_ORDER.indexOf(candidate);
|
|
66
|
+
for (let j = i; j < AGY_EFFORT_ORDER.length; j++) {
|
|
67
|
+
if (efforts.includes(AGY_EFFORT_ORDER[j])) return AGY_EFFORT_ORDER[j];
|
|
68
|
+
}
|
|
69
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
70
|
+
if (efforts.includes(AGY_EFFORT_ORDER[j])) return AGY_EFFORT_ORDER[j];
|
|
71
|
+
}
|
|
72
|
+
return efforts[0] ?? "low";
|
|
73
|
+
}
|
|
74
|
+
|
|
34
75
|
/** Split an agy slug into (base, tier). tier is null when the slug has no
|
|
35
76
|
* -high/-medium/-low suffix (claude-sonnet-4-6, gpt-oss-120b-medium's "medium"
|
|
36
77
|
* IS its suffix here, claude-opus-4-6-thinking is not a tier). */
|
package/src/provider.ts
CHANGED
|
@@ -33,7 +33,7 @@ import type { Api } from "@earendil-works/pi-ai";
|
|
|
33
33
|
import type { DriverActivity, TurnDriver, TurnHandle } from "./driver-types.js";
|
|
34
34
|
import { toPiUsage } from "./stream-events.js";
|
|
35
35
|
import { mapAgyToolToNative } from "./native-tools.js";
|
|
36
|
-
import {
|
|
36
|
+
import { toAgyEffort, type AgyModelEntry } from "./models.js";
|
|
37
37
|
import { SessionStore } from "./sessions.js";
|
|
38
38
|
import { loadConfig } from "./config.js";
|
|
39
39
|
import { GATE_MARKER, mapNativeToShadow, stripMarkerFields } from "./approval-gate.js";
|
|
@@ -338,46 +338,6 @@ export interface StreamSimpleDeps {
|
|
|
338
338
|
log?: (event: string, data?: unknown, level?: "debug" | "info" | "warn" | "error") => void;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
/** pi thinking-effort order mirrors agy's, for clamping. */
|
|
342
|
-
const AGY_EFFORT_ORDER: readonly AgyEffort[] = ["low", "medium", "high"];
|
|
343
|
-
|
|
344
|
-
/** Map pi's thinking level onto one of the tiers `efforts` the base actually
|
|
345
|
-
* supports, clamping to the nearest available. agy rejects an effort tier a
|
|
346
|
-
* base doesn't list (e.g. medium on Pro), so we never emit one. A base slug is
|
|
347
|
-
* invalid without --effort, so when pi sends no level we default to the
|
|
348
|
-
* middle tier (or the highest available). */
|
|
349
|
-
export function toAgyEffort(
|
|
350
|
-
reasoning: ThinkingLevel | undefined,
|
|
351
|
-
efforts: readonly AgyEffort[],
|
|
352
|
-
): AgyEffort {
|
|
353
|
-
let candidate: AgyEffort;
|
|
354
|
-
switch (reasoning) {
|
|
355
|
-
case "minimal":
|
|
356
|
-
case "low":
|
|
357
|
-
candidate = "low";
|
|
358
|
-
break;
|
|
359
|
-
case "medium":
|
|
360
|
-
candidate = "medium";
|
|
361
|
-
break;
|
|
362
|
-
case "high":
|
|
363
|
-
case "xhigh":
|
|
364
|
-
case "max":
|
|
365
|
-
candidate = "high";
|
|
366
|
-
break;
|
|
367
|
-
default:
|
|
368
|
-
candidate = efforts[0] ?? "low";
|
|
369
|
-
}
|
|
370
|
-
if (efforts.includes(candidate)) return candidate;
|
|
371
|
-
const i = AGY_EFFORT_ORDER.indexOf(candidate);
|
|
372
|
-
for (let j = i; j < AGY_EFFORT_ORDER.length; j++) {
|
|
373
|
-
if (efforts.includes(AGY_EFFORT_ORDER[j])) return AGY_EFFORT_ORDER[j];
|
|
374
|
-
}
|
|
375
|
-
for (let j = i - 1; j >= 0; j--) {
|
|
376
|
-
if (efforts.includes(AGY_EFFORT_ORDER[j])) return AGY_EFFORT_ORDER[j];
|
|
377
|
-
}
|
|
378
|
-
return efforts[0] ?? "low";
|
|
379
|
-
}
|
|
380
|
-
|
|
381
341
|
// --- G9: no-patch pi-tool round-trips -----------------------------------------
|
|
382
342
|
//
|
|
383
343
|
// The MCP bridge's onToolCall parks the call here instead of executing it:
|