@mrclrchtr/supi-antigravity 6.4.0
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/CLAUDE.md +21 -0
- package/CONTEXT.md +53 -0
- package/README.md +75 -0
- package/docs/adr/0001-use-an-isolated-antigravity-home.md +5 -0
- package/node_modules/@mrclrchtr/supi-core/README.md +118 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +76 -0
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +40 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +232 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +363 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +12 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/context.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +308 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +120 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +14 -0
- package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +57 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +34 -0
- package/node_modules/@mrclrchtr/supi-core/src/llm.ts +201 -0
- package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
- package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +44 -0
- package/node_modules/@mrclrchtr/supi-core/src/path.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/project.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +93 -0
- package/node_modules/@mrclrchtr/supi-core/src/report.ts +121 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +71 -0
- package/node_modules/@mrclrchtr/supi-core/src/session.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +105 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +453 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/package.json +79 -0
- package/scripts/live-probe.ts +161 -0
- package/src/activity.ts +22 -0
- package/src/availability.ts +231 -0
- package/src/catalogue.ts +21 -0
- package/src/config.ts +26 -0
- package/src/conversation/handles.ts +183 -0
- package/src/extension.ts +22 -0
- package/src/isolated-home.ts +346 -0
- package/src/process/environment.ts +33 -0
- package/src/process/event-values.ts +279 -0
- package/src/process/events.ts +365 -0
- package/src/process/hooks.ts +219 -0
- package/src/process/ndjson.ts +120 -0
- package/src/process/protocol.ts +69 -0
- package/src/process/runner.ts +147 -0
- package/src/process/subprocess.ts +278 -0
- package/src/process/usage.ts +50 -0
- package/src/runtime.ts +118 -0
- package/src/settings.ts +38 -0
- package/src/structured-output.ts +58 -0
- package/src/tool/antigravity_run/evidence.ts +169 -0
- package/src/tool/antigravity_run/execute.ts +225 -0
- package/src/tool/antigravity_run/guidance.ts +3 -0
- package/src/tool/antigravity_run/input.ts +106 -0
- package/src/tool/antigravity_run/register.ts +36 -0
- package/src/tool/antigravity_run/render.ts +236 -0
- package/src/tool/antigravity_run/result.ts +186 -0
- package/src/tool/antigravity_run/spec.ts +20 -0
- package/src/types.ts +107 -0
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mrclrchtr/supi-antigravity",
|
|
3
|
+
"version": "6.4.0",
|
|
4
|
+
"description": "Bounded Antigravity tasks with isolated workspace access and evidence",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/mrclrchtr/supi.git",
|
|
9
|
+
"directory": "packages/supi-antigravity"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/mrclrchtr/supi/tree/main/packages/supi-antigravity#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/mrclrchtr/supi/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"pi-package",
|
|
20
|
+
"pi",
|
|
21
|
+
"pi-coding-agent",
|
|
22
|
+
"pi-extension",
|
|
23
|
+
"supi",
|
|
24
|
+
"antigravity",
|
|
25
|
+
"agent",
|
|
26
|
+
"web",
|
|
27
|
+
"workspace"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"files": [
|
|
31
|
+
"src/**/*.ts",
|
|
32
|
+
"scripts/live-probe.ts",
|
|
33
|
+
"README.md",
|
|
34
|
+
"CLAUDE.md",
|
|
35
|
+
"CONTEXT.md",
|
|
36
|
+
"docs/adr/*.md"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@mrclrchtr/supi-core": "workspace:*"
|
|
40
|
+
},
|
|
41
|
+
"bundledDependencies": [
|
|
42
|
+
"@mrclrchtr/supi-core"
|
|
43
|
+
],
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@earendil-works/pi-ai": "*",
|
|
46
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
47
|
+
"@earendil-works/pi-tui": "*",
|
|
48
|
+
"typebox": "*"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"@earendil-works/pi-ai": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"@earendil-works/pi-coding-agent": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@earendil-works/pi-tui": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"typebox": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@mrclrchtr/supi-test-utils": "workspace:*",
|
|
66
|
+
"@types/node": "25.9.5",
|
|
67
|
+
"vitest": "4.1.11"
|
|
68
|
+
},
|
|
69
|
+
"pi": {
|
|
70
|
+
"extensions": [
|
|
71
|
+
"./src/extension.ts"
|
|
72
|
+
],
|
|
73
|
+
"image": "https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-antigravity/assets/social-preview.png"
|
|
74
|
+
},
|
|
75
|
+
"exports": {
|
|
76
|
+
"./extension": "./src/extension.ts",
|
|
77
|
+
"./package.json": "./package.json"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { cp, lstat, mkdtemp, readdir, readFile, readlink, realpath, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, relative } from "node:path";
|
|
5
|
+
import { isWebToolName, isWorkspaceToolName } from "../src/activity.ts";
|
|
6
|
+
import { discoverAntigravityAvailability } from "../src/availability.ts";
|
|
7
|
+
import { ConversationHandleStore } from "../src/conversation/handles.ts";
|
|
8
|
+
import { getIsolatedAntigravityPaths } from "../src/isolated-home.ts";
|
|
9
|
+
import { runAntigravityConversation } from "../src/process/runner.ts";
|
|
10
|
+
import { ANTIGRAVITY_ANSWER_SCHEMA } from "../src/structured-output.ts";
|
|
11
|
+
import { isHttpUrl, isSafeWorkspacePath } from "../src/tool/antigravity_run/evidence.ts";
|
|
12
|
+
import type { CuratedModel } from "../src/types.ts";
|
|
13
|
+
|
|
14
|
+
const LIVE_ENVIRONMENT = "SUPI_ANTIGRAVITY_LIVE";
|
|
15
|
+
const MODEL: CuratedModel = "gemini-3.8-flash-low";
|
|
16
|
+
const CONTEXT_TOKEN = "supi-antigravity-context-token";
|
|
17
|
+
|
|
18
|
+
async function main(): Promise<void> {
|
|
19
|
+
if (process.env[LIVE_ENVIRONMENT] !== "1") {
|
|
20
|
+
throw new Error(`Set ${LIVE_ENVIRONMENT}=1 to run the Antigravity live probe.`);
|
|
21
|
+
}
|
|
22
|
+
const availability = await discoverAntigravityAvailability();
|
|
23
|
+
if (availability.status !== "available" || !availability.catalogue.includes(MODEL)) {
|
|
24
|
+
throw new Error("Gemini 3.8 Flash Low is not available in the isolated Antigravity account.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const paths = getIsolatedAntigravityPaths();
|
|
28
|
+
const handles = new ConversationHandleStore();
|
|
29
|
+
const first = await runAntigravityConversation({
|
|
30
|
+
paths,
|
|
31
|
+
cwd: paths.consultationWorkspace,
|
|
32
|
+
prompt: `Remember the exact token ${CONTEXT_TOKEN}. Name one current official Node.js documentation page about subprocess cancellation and cite it.`,
|
|
33
|
+
model: MODEL,
|
|
34
|
+
schema: ANTIGRAVITY_ANSWER_SCHEMA as Record<string, unknown>,
|
|
35
|
+
});
|
|
36
|
+
requireWebEvidence(first);
|
|
37
|
+
const record = handles.create({
|
|
38
|
+
rawAntigravityId: first.conversationId,
|
|
39
|
+
model: MODEL,
|
|
40
|
+
canonicalWorkingDirectory: paths.consultationWorkspace,
|
|
41
|
+
workspaceAccess: false,
|
|
42
|
+
cliVersion: availability.cliVersion,
|
|
43
|
+
});
|
|
44
|
+
const second = await runAntigravityConversation({
|
|
45
|
+
paths,
|
|
46
|
+
cwd: paths.consultationWorkspace,
|
|
47
|
+
prompt: `What exact context token did I give you? Answer in one sentence and include ${CONTEXT_TOKEN}.`,
|
|
48
|
+
model: MODEL,
|
|
49
|
+
conversationId: record.rawAntigravityId,
|
|
50
|
+
schema: ANTIGRAVITY_ANSWER_SCHEMA as Record<string, unknown>,
|
|
51
|
+
});
|
|
52
|
+
if (
|
|
53
|
+
second.conversationId !== record.rawAntigravityId ||
|
|
54
|
+
!second.answer.answer.includes(CONTEXT_TOKEN)
|
|
55
|
+
) {
|
|
56
|
+
throw new Error("Antigravity follow-up did not retain the Conversation Handle context.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const fixture = await realpath(await mkdtemp(join(tmpdir(), "supi-antigravity-live-")));
|
|
60
|
+
try {
|
|
61
|
+
await cp(join(import.meta.dirname, "../__tests__/fixtures/subprocess-cancellation"), fixture, {
|
|
62
|
+
recursive: true,
|
|
63
|
+
});
|
|
64
|
+
const before = await digestTree(fixture);
|
|
65
|
+
const workspace = await runAntigravityConversation({
|
|
66
|
+
paths,
|
|
67
|
+
cwd: fixture,
|
|
68
|
+
prompt:
|
|
69
|
+
"Inspect the TypeScript subprocess-cancellation fixture. Consult official Node.js documentation and explain the cancellation risk. Return relative file paths within the fixture and source URLs. Do not return absolute file paths.",
|
|
70
|
+
model: MODEL,
|
|
71
|
+
workspaceDirectory: fixture,
|
|
72
|
+
schema: ANTIGRAVITY_ANSWER_SCHEMA as Record<string, unknown>,
|
|
73
|
+
});
|
|
74
|
+
requireWebEvidence(workspace);
|
|
75
|
+
requireWorkspaceEvidence(workspace, fixture);
|
|
76
|
+
if (!workspace.successfulToolNames.some(isWorkspaceToolName)) {
|
|
77
|
+
throw new Error("The workspace probe did not observe workspace activity.");
|
|
78
|
+
}
|
|
79
|
+
const after = await digestTree(fixture);
|
|
80
|
+
if (before !== after) throw new Error("The live probe changed the fixture.");
|
|
81
|
+
} finally {
|
|
82
|
+
await rm(fixture, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function requireWebEvidence(result: {
|
|
87
|
+
successfulToolNames: string[];
|
|
88
|
+
answer: { sources: unknown[] };
|
|
89
|
+
}): void {
|
|
90
|
+
const web = result.successfulToolNames.some(isWebToolName);
|
|
91
|
+
if (!web || result.answer.sources.length === 0) {
|
|
92
|
+
throw new Error("The live probe did not observe web evidence.");
|
|
93
|
+
}
|
|
94
|
+
if (
|
|
95
|
+
!result.answer.sources.every(
|
|
96
|
+
(source) => isRecord(source) && typeof source.url === "string" && isHttpUrl(source.url),
|
|
97
|
+
)
|
|
98
|
+
) {
|
|
99
|
+
throw new Error("The live probe returned an invalid source URL.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function requireWorkspaceEvidence(
|
|
104
|
+
result: { answer: { workspaceEvidence: unknown[] } },
|
|
105
|
+
workspace: string,
|
|
106
|
+
): void {
|
|
107
|
+
if (
|
|
108
|
+
result.answer.workspaceEvidence.length === 0 ||
|
|
109
|
+
!result.answer.workspaceEvidence.every(
|
|
110
|
+
(item) =>
|
|
111
|
+
isRecord(item) &&
|
|
112
|
+
typeof item.path === "string" &&
|
|
113
|
+
isSafeWorkspacePath(item.path, workspace),
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
throw new Error("The live probe did not return valid workspace evidence.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function digestTree(directory: string): Promise<string> {
|
|
125
|
+
const hash = createHash("sha256");
|
|
126
|
+
await digestDirectory(directory, directory, hash);
|
|
127
|
+
return hash.digest("hex");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function digestDirectory(
|
|
131
|
+
directory: string,
|
|
132
|
+
root: string,
|
|
133
|
+
hash: ReturnType<typeof createHash>,
|
|
134
|
+
): Promise<void> {
|
|
135
|
+
const entries = (await readdir(directory, { withFileTypes: true })).sort((left, right) =>
|
|
136
|
+
left.name.localeCompare(right.name),
|
|
137
|
+
);
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const file = join(directory, entry.name);
|
|
140
|
+
hash.update(relative(root, file));
|
|
141
|
+
if (entry.isDirectory()) {
|
|
142
|
+
hash.update("directory");
|
|
143
|
+
await digestDirectory(file, root, hash);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (entry.isSymbolicLink()) {
|
|
147
|
+
hash.update("link");
|
|
148
|
+
hash.update(await readlink(file));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (entry.isFile()) {
|
|
152
|
+
hash.update("file");
|
|
153
|
+
hash.update(await readFile(file));
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const info = await lstat(file);
|
|
157
|
+
hash.update(`${info.mode}:${info.size}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await main();
|
package/src/activity.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { normalizeToolName } from "./process/event-values.ts";
|
|
2
|
+
|
|
3
|
+
/** Return whether a successful Antigravity tool name represents web activity. */
|
|
4
|
+
export function isWebToolName(name: string): boolean {
|
|
5
|
+
const normalized = normalizeToolName(name);
|
|
6
|
+
return ["search_web", "read_url_content", "read_url", "web_search"].includes(normalized);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Return whether a successful Antigravity tool name represents workspace activity. */
|
|
10
|
+
export function isWorkspaceToolName(name: string): boolean {
|
|
11
|
+
const normalized = normalizeToolName(name);
|
|
12
|
+
return (
|
|
13
|
+
normalized.includes("file") ||
|
|
14
|
+
normalized.includes("directory") ||
|
|
15
|
+
normalized.includes("code_search") ||
|
|
16
|
+
normalized.includes("search_code") ||
|
|
17
|
+
normalized.includes("grep") ||
|
|
18
|
+
normalized.includes("find") ||
|
|
19
|
+
normalized.includes("glob") ||
|
|
20
|
+
["list_files", "list_dir", "read_file"].includes(normalized)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatAntigravityLoginCommand,
|
|
3
|
+
getIsolatedAntigravityPaths,
|
|
4
|
+
type IsolatedAntigravityPaths,
|
|
5
|
+
} from "./isolated-home.ts";
|
|
6
|
+
import type { AntigravityProbeResult } from "./process/runner.ts";
|
|
7
|
+
import { AntigravityProcessError, runAntigravityProbe } from "./process/runner.ts";
|
|
8
|
+
import { CURATED_MODELS, type CuratedModel, isCuratedModel } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
/** Minimum supported Antigravity CLI version. */
|
|
11
|
+
export const MINIMUM_ANTIGRAVITY_VERSION = Object.freeze({ major: 1, minor: 1, patch: 24 });
|
|
12
|
+
const MAX_DISCOVERY_OUTPUT_BYTES = 128 * 1024;
|
|
13
|
+
|
|
14
|
+
/** Parsed Antigravity CLI version. */
|
|
15
|
+
export interface AntigravityVersion {
|
|
16
|
+
major: number;
|
|
17
|
+
minor: number;
|
|
18
|
+
patch: number;
|
|
19
|
+
prerelease?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A successful immutable availability snapshot. */
|
|
23
|
+
export interface AvailableAntigravity {
|
|
24
|
+
status: "available";
|
|
25
|
+
cliVersion: string;
|
|
26
|
+
catalogue: readonly CuratedModel[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A bounded reason why the tool cannot be registered. */
|
|
30
|
+
export interface UnavailableAntigravity {
|
|
31
|
+
status: "unavailable";
|
|
32
|
+
reason: "missing" | "old-version" | "authentication" | "discovery" | "no-curated-model";
|
|
33
|
+
warning: string;
|
|
34
|
+
cliVersion?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Result of one immutable Antigravity availability discovery. */
|
|
38
|
+
export type AntigravityAvailability = AvailableAntigravity | UnavailableAntigravity;
|
|
39
|
+
|
|
40
|
+
/** Parse the first semantic version in CLI output. */
|
|
41
|
+
export function parseAntigravityVersion(output: string): AntigravityVersion | undefined {
|
|
42
|
+
const match = output.match(/\b(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?\b/);
|
|
43
|
+
if (!match) return undefined;
|
|
44
|
+
return {
|
|
45
|
+
major: Number(match[1]),
|
|
46
|
+
minor: Number(match[2]),
|
|
47
|
+
patch: Number(match[3]),
|
|
48
|
+
...(match[4] ? { prerelease: match[4] } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Return whether a parsed version meets the supported minimum. */
|
|
53
|
+
export function isSupportedAntigravityVersion(version: AntigravityVersion): boolean {
|
|
54
|
+
const minimum = MINIMUM_ANTIGRAVITY_VERSION;
|
|
55
|
+
if (version.major !== minimum.major) return version.major > minimum.major;
|
|
56
|
+
if (version.minor !== minimum.minor) return version.minor > minimum.minor;
|
|
57
|
+
if (version.patch !== minimum.patch) return version.patch > minimum.patch;
|
|
58
|
+
return version.prerelease === undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Format a version without retaining the CLI's complete output. */
|
|
62
|
+
export function formatAntigravityVersion(version: AntigravityVersion): string {
|
|
63
|
+
return `${version.major}.${version.minor}.${version.patch}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Parse bounded tab-separated `agy models` output. */
|
|
67
|
+
export function parseAvailableModels(output: string): string[] {
|
|
68
|
+
if (Buffer.byteLength(output, "utf8") > MAX_DISCOVERY_OUTPUT_BYTES) {
|
|
69
|
+
throw new Error("Antigravity model output exceeded its limit.");
|
|
70
|
+
}
|
|
71
|
+
const found = new Set<string>();
|
|
72
|
+
for (const line of output.split(/\r?\n/)) {
|
|
73
|
+
for (const field of line.split("\t")) {
|
|
74
|
+
const model = field.trim();
|
|
75
|
+
if (isCuratedModel(model)) found.add(model);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [...found];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Intersect discovered IDs with the immutable curated model order. */
|
|
82
|
+
export function intersectCuratedModels(discovered: Iterable<string>): readonly CuratedModel[] {
|
|
83
|
+
const available = new Set(discovered);
|
|
84
|
+
return Object.freeze(CURATED_MODELS.filter((model) => available.has(model)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Discover the supported Antigravity models in the Isolated Antigravity Home. */
|
|
88
|
+
export async function discoverAntigravityAvailability(
|
|
89
|
+
options: {
|
|
90
|
+
paths?: IsolatedAntigravityPaths;
|
|
91
|
+
signal?: AbortSignal;
|
|
92
|
+
runProbe?: typeof runAntigravityProbe;
|
|
93
|
+
} = {},
|
|
94
|
+
): Promise<AntigravityAvailability> {
|
|
95
|
+
const paths = options.paths ?? getIsolatedAntigravityPaths();
|
|
96
|
+
const runProbe = options.runProbe ?? runAntigravityProbe;
|
|
97
|
+
let versionResult: AntigravityProbeResult;
|
|
98
|
+
try {
|
|
99
|
+
versionResult = await runProbe({
|
|
100
|
+
paths,
|
|
101
|
+
cwd: paths.consultationWorkspace,
|
|
102
|
+
args: ["--version"],
|
|
103
|
+
signal: options.signal,
|
|
104
|
+
timeoutMs: 15_000,
|
|
105
|
+
maxStdoutBytes: MAX_DISCOVERY_OUTPUT_BYTES,
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return unavailableFromError(error, paths);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const version = parseAntigravityVersion(`${versionResult.stdout}\n${versionResult.stderr}`);
|
|
112
|
+
if (!version || versionResult.exitCode !== 0 || versionResult.signal) {
|
|
113
|
+
return {
|
|
114
|
+
status: "unavailable",
|
|
115
|
+
reason: "discovery",
|
|
116
|
+
warning:
|
|
117
|
+
"Antigravity CLI version could not be read. Install or update Antigravity, then reload PI.",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const cliVersion = formatAntigravityVersion(version);
|
|
121
|
+
if (!isSupportedAntigravityVersion(version)) {
|
|
122
|
+
return {
|
|
123
|
+
status: "unavailable",
|
|
124
|
+
reason: "old-version",
|
|
125
|
+
cliVersion,
|
|
126
|
+
warning: `Antigravity CLI ${cliVersion} is too old; version ${formatAntigravityVersion(MINIMUM_ANTIGRAVITY_VERSION)} or newer is required.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let modelsResult: AntigravityProbeResult;
|
|
131
|
+
try {
|
|
132
|
+
modelsResult = await runProbe({
|
|
133
|
+
paths,
|
|
134
|
+
cwd: paths.consultationWorkspace,
|
|
135
|
+
args: ["models"],
|
|
136
|
+
signal: options.signal,
|
|
137
|
+
timeoutMs: 30_000,
|
|
138
|
+
maxStdoutBytes: MAX_DISCOVERY_OUTPUT_BYTES,
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return unavailableFromError(error, paths, cliVersion);
|
|
142
|
+
}
|
|
143
|
+
const modelOutput = `${modelsResult.stdout}\n${modelsResult.stderr}`;
|
|
144
|
+
if (looksUnauthenticated(modelOutput)) return authenticationUnavailable(paths, cliVersion);
|
|
145
|
+
if (modelsResult.exitCode !== 0 || modelsResult.signal) {
|
|
146
|
+
return {
|
|
147
|
+
status: "unavailable",
|
|
148
|
+
reason: "discovery",
|
|
149
|
+
cliVersion,
|
|
150
|
+
warning:
|
|
151
|
+
"Antigravity model discovery failed. Check the isolated Antigravity sign-in, then reload PI.",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let catalogue: readonly CuratedModel[];
|
|
156
|
+
try {
|
|
157
|
+
catalogue = intersectCuratedModels(parseAvailableModels(modelsResult.stdout));
|
|
158
|
+
} catch {
|
|
159
|
+
return {
|
|
160
|
+
status: "unavailable",
|
|
161
|
+
reason: "discovery",
|
|
162
|
+
cliVersion,
|
|
163
|
+
warning: "Antigravity model discovery output exceeded its limit.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (catalogue.length === 0) {
|
|
167
|
+
return {
|
|
168
|
+
status: "unavailable",
|
|
169
|
+
reason: "no-curated-model",
|
|
170
|
+
cliVersion,
|
|
171
|
+
warning: "No supported curated Antigravity model is available to this account.",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return { status: "available", cliVersion, catalogue };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function unavailableFromError(
|
|
178
|
+
error: unknown,
|
|
179
|
+
paths: IsolatedAntigravityPaths,
|
|
180
|
+
cliVersion?: string,
|
|
181
|
+
): UnavailableAntigravity {
|
|
182
|
+
if (error instanceof AntigravityProcessError && error.kind === "missing") {
|
|
183
|
+
return {
|
|
184
|
+
status: "unavailable",
|
|
185
|
+
reason: "missing",
|
|
186
|
+
warning: "Antigravity CLI `agy` was not found on PATH. Install it, then reload PI.",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (
|
|
190
|
+
looksUnauthenticated(
|
|
191
|
+
error instanceof AntigravityProcessError
|
|
192
|
+
? `${error.message}\n${error.stderr ?? ""}`
|
|
193
|
+
: error instanceof Error
|
|
194
|
+
? error.message
|
|
195
|
+
: "",
|
|
196
|
+
)
|
|
197
|
+
) {
|
|
198
|
+
return authenticationUnavailable(paths, cliVersion);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
status: "unavailable",
|
|
202
|
+
reason: "discovery",
|
|
203
|
+
...(cliVersion ? { cliVersion } : {}),
|
|
204
|
+
warning: "Antigravity availability discovery failed. Check the installation, then reload PI.",
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function authenticationUnavailable(
|
|
209
|
+
paths: IsolatedAntigravityPaths,
|
|
210
|
+
cliVersion?: string,
|
|
211
|
+
): UnavailableAntigravity {
|
|
212
|
+
return {
|
|
213
|
+
status: "unavailable",
|
|
214
|
+
reason: "authentication",
|
|
215
|
+
...(cliVersion ? { cliVersion } : {}),
|
|
216
|
+
warning: `Antigravity needs sign-in in its Isolated Antigravity Home. Run:\n${formatAntigravityLoginCommand(paths)}\nThen exit Antigravity and reload PI.`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function looksUnauthenticated(value: string): boolean {
|
|
221
|
+
const normalized = value.toLowerCase();
|
|
222
|
+
return [
|
|
223
|
+
"please sign in",
|
|
224
|
+
"not authenticated",
|
|
225
|
+
"authentication required",
|
|
226
|
+
"unauthorized",
|
|
227
|
+
"sign-in required",
|
|
228
|
+
"login required",
|
|
229
|
+
"log in",
|
|
230
|
+
].some((marker) => normalized.includes(marker));
|
|
231
|
+
}
|
package/src/catalogue.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { TSchema } from "typebox";
|
|
3
|
+
import type { CuratedModel } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
/** Build the provider-facing enum from one discovered Model Catalogue. */
|
|
6
|
+
export function buildModelCatalogueEnum(catalogue: readonly CuratedModel[]): TSchema {
|
|
7
|
+
if (catalogue.length === 0) {
|
|
8
|
+
throw new Error("Cannot build an Antigravity model enum from an empty catalogue.");
|
|
9
|
+
}
|
|
10
|
+
return StringEnum([...catalogue] as [string, ...string[]], {
|
|
11
|
+
description: "Curated Antigravity model available to the current account.",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Check runtime model input against the same discovered catalogue. */
|
|
16
|
+
export function isAvailableCuratedModel(
|
|
17
|
+
value: unknown,
|
|
18
|
+
catalogue: readonly CuratedModel[],
|
|
19
|
+
): value is CuratedModel {
|
|
20
|
+
return typeof value === "string" && catalogue.includes(value as CuratedModel);
|
|
21
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { loadSupiConfig } from "@mrclrchtr/supi-core/config";
|
|
2
|
+
|
|
3
|
+
/** The persisted SuPi configuration section owned by this package. */
|
|
4
|
+
export const ANTIGRAVITY_CONFIG_SECTION = "antigravity";
|
|
5
|
+
|
|
6
|
+
/** Configuration for the Antigravity Run tool. */
|
|
7
|
+
export interface AntigravityConfig extends Record<string, unknown> {
|
|
8
|
+
/** Enable availability discovery and the antigravity_run tool. */
|
|
9
|
+
agentToolEnabled: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Default Antigravity configuration. */
|
|
13
|
+
export const ANTIGRAVITY_DEFAULTS: AntigravityConfig = Object.freeze({
|
|
14
|
+
agentToolEnabled: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/** Load the effective Antigravity configuration for a workspace. */
|
|
18
|
+
export function loadAntigravityConfig(cwd: string, homeDir?: string): AntigravityConfig {
|
|
19
|
+
const raw = loadSupiConfig(ANTIGRAVITY_CONFIG_SECTION, cwd, ANTIGRAVITY_DEFAULTS, { homeDir });
|
|
20
|
+
return {
|
|
21
|
+
agentToolEnabled:
|
|
22
|
+
typeof raw.agentToolEnabled === "boolean"
|
|
23
|
+
? raw.agentToolEnabled
|
|
24
|
+
: ANTIGRAVITY_DEFAULTS.agentToolEnabled,
|
|
25
|
+
};
|
|
26
|
+
}
|