@cortexkit/pi-antigravity-auth 2.2.1 → 3.0.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/README.md +50 -10
- package/dist/commands.d.ts +30 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/convert.d.ts +4 -2
- package/dist/convert.d.ts.map +1 -1
- package/dist/index.d.ts +31 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1083 -45
- package/dist/index.js.map +4 -4
- package/dist/paths.d.ts +11 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/stream.d.ts +25 -3
- package/dist/stream.d.ts.map +1 -1
- package/dist/vault-setup.d.ts +144 -0
- package/dist/vault-setup.d.ts.map +1 -0
- package/dist/vault-source.d.ts +60 -0
- package/dist/vault-source.d.ts.map +1 -0
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -1,10 +1,553 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { join as join2 } from "node:path";
|
|
2
3
|
import {
|
|
4
|
+
ANTIGRAVITY_PROVIDER_ID as ANTIGRAVITY_PROVIDER_ID2,
|
|
3
5
|
authorizeAntigravity,
|
|
6
|
+
enrollmentAuthority as enrollmentAuthority3,
|
|
4
7
|
exchangeAntigravity,
|
|
5
8
|
getPublicModelDefinitions,
|
|
9
|
+
loadCommonAuthPiSlot,
|
|
6
10
|
refreshAntigravityToken
|
|
7
11
|
} from "@cortexkit/antigravity-auth-core";
|
|
12
|
+
import {
|
|
13
|
+
getAgentDir,
|
|
14
|
+
readStoredCredential
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
16
|
+
|
|
17
|
+
// src/commands.ts
|
|
18
|
+
import {
|
|
19
|
+
ANTIGRAVITY_MENU_COMMAND,
|
|
20
|
+
createAntigravityCommandMenu,
|
|
21
|
+
loadCommonAuthCommands
|
|
22
|
+
} from "@cortexkit/antigravity-auth-core";
|
|
23
|
+
|
|
24
|
+
// src/vault-setup.ts
|
|
25
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
26
|
+
import { readFileSync } from "node:fs";
|
|
27
|
+
import {
|
|
28
|
+
ANTIGRAVITY_PROVIDER_ID,
|
|
29
|
+
enrollmentAuthority
|
|
30
|
+
} from "@cortexkit/antigravity-auth-core";
|
|
31
|
+
import { writeJsonAtomic } from "@cortexkit/antigravity-auth-core/atomic-write";
|
|
32
|
+
var PI_ANTIGRAVITY_PROVIDER_ID = ANTIGRAVITY_PROVIDER_ID;
|
|
33
|
+
var PI_ANTIGRAVITY_VAULT_HOST = "pi";
|
|
34
|
+
var PI_ANTIGRAVITY_ENROLLMENT_NAME = "antigravity-auth-pi";
|
|
35
|
+
var LOCAL_MODE = Object.freeze({
|
|
36
|
+
version: 1,
|
|
37
|
+
mode: "local"
|
|
38
|
+
});
|
|
39
|
+
function parseModeRecord(raw) {
|
|
40
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
41
|
+
return void 0;
|
|
42
|
+
const record = raw;
|
|
43
|
+
for (const key of Object.keys(record))
|
|
44
|
+
if (key !== "version" && key !== "mode" && key !== "routeId")
|
|
45
|
+
return void 0;
|
|
46
|
+
if (record.version !== 1) return void 0;
|
|
47
|
+
if (record.mode !== "local" && record.mode !== "custody") return void 0;
|
|
48
|
+
if (record.routeId === void 0)
|
|
49
|
+
return Object.freeze({ version: 1, mode: record.mode });
|
|
50
|
+
if (typeof record.routeId !== "string" || record.routeId.trim() === "")
|
|
51
|
+
return void 0;
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
version: 1,
|
|
54
|
+
mode: record.mode,
|
|
55
|
+
routeId: record.routeId
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function readPiVaultMode(path) {
|
|
59
|
+
let text;
|
|
60
|
+
try {
|
|
61
|
+
text = readFileSync(path, "utf8");
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error?.code === "ENOENT")
|
|
64
|
+
return { ok: true, record: LOCAL_MODE };
|
|
65
|
+
return { ok: false, reason: "the vault mode file could not be read" };
|
|
66
|
+
}
|
|
67
|
+
let raw;
|
|
68
|
+
try {
|
|
69
|
+
raw = JSON.parse(text);
|
|
70
|
+
} catch {
|
|
71
|
+
return { ok: false, reason: "the vault mode file is not valid JSON" };
|
|
72
|
+
}
|
|
73
|
+
const record = parseModeRecord(raw);
|
|
74
|
+
return record ? { ok: true, record } : { ok: false, reason: "the vault mode file has an unknown shape" };
|
|
75
|
+
}
|
|
76
|
+
async function writePiVaultMode(path, record) {
|
|
77
|
+
const value = record.routeId === void 0 ? { version: 1, mode: record.mode } : { version: 1, mode: record.mode, routeId: record.routeId };
|
|
78
|
+
await writeJsonAtomic(path, value);
|
|
79
|
+
}
|
|
80
|
+
var PiVaultSetupError = class extends Error {
|
|
81
|
+
constructor(message) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = "PiVaultSetupError";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var PiVaultSetup = class {
|
|
87
|
+
#options;
|
|
88
|
+
paths;
|
|
89
|
+
constructor(options) {
|
|
90
|
+
this.#options = options;
|
|
91
|
+
this.paths = options.claustrum.hostEnrollmentPaths({
|
|
92
|
+
stateDir: options.stateDir,
|
|
93
|
+
host: PI_ANTIGRAVITY_VAULT_HOST
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
#hostSlot() {
|
|
97
|
+
return this.#options.claustrum.classifyHostSlot(
|
|
98
|
+
this.#options.readHostSlot(),
|
|
99
|
+
PI_ANTIGRAVITY_PROVIDER_ID
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
/** Read vault approval and local/vault settings without opening Pi's Google credentials. */
|
|
103
|
+
async sourceStatus() {
|
|
104
|
+
const mode = readPiVaultMode(this.#options.modeFile);
|
|
105
|
+
const enrollment = await this.#options.claustrum.readClaustrumEnrollmentStatus(this.paths, PI_ANTIGRAVITY_ENROLLMENT_NAME).catch(
|
|
106
|
+
() => ({
|
|
107
|
+
state: "blocked",
|
|
108
|
+
proposedName: PI_ANTIGRAVITY_ENROLLMENT_NAME,
|
|
109
|
+
code: "unreadable-state"
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
const authority = await enrollmentAuthority(
|
|
113
|
+
this.paths,
|
|
114
|
+
PI_ANTIGRAVITY_ENROLLMENT_NAME,
|
|
115
|
+
this.#options.claustrum
|
|
116
|
+
);
|
|
117
|
+
return {
|
|
118
|
+
...authority !== "local" ? {
|
|
119
|
+
mode: authority === "vault" ? "custody" : "unreadable",
|
|
120
|
+
...mode.ok && mode.record.routeId !== void 0 && {
|
|
121
|
+
selectedRouteId: mode.record.routeId
|
|
122
|
+
}
|
|
123
|
+
} : mode.ok ? {
|
|
124
|
+
mode: mode.record.mode,
|
|
125
|
+
...mode.record.routeId !== void 0 && {
|
|
126
|
+
selectedRouteId: mode.record.routeId
|
|
127
|
+
}
|
|
128
|
+
} : { mode: "unreadable", modeProblem: mode.reason },
|
|
129
|
+
enrollment
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Inspect the native login only when local accounts are selected. */
|
|
133
|
+
async status() {
|
|
134
|
+
const source = await this.sourceStatus();
|
|
135
|
+
return {
|
|
136
|
+
...source,
|
|
137
|
+
hostSlot: source.mode === "local" ? this.#hostSlot() : "dormant"
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Propose Pi to the vault or poll its recorded proposal. Clear a denied or
|
|
142
|
+
* blocked request before starting again. Only setup actions call this method.
|
|
143
|
+
*/
|
|
144
|
+
async connectStep() {
|
|
145
|
+
const client = await this.#options.connectEnrollment();
|
|
146
|
+
try {
|
|
147
|
+
const manager = new this.#options.claustrum.ClaustrumEnrollmentManager({
|
|
148
|
+
client,
|
|
149
|
+
paths: this.paths,
|
|
150
|
+
proposedName: PI_ANTIGRAVITY_ENROLLMENT_NAME,
|
|
151
|
+
...this.#options.now && { now: this.#options.now }
|
|
152
|
+
});
|
|
153
|
+
const current = await manager.status();
|
|
154
|
+
if (current.state === "denied" || current.state === "blocked")
|
|
155
|
+
await manager.resetTerminal();
|
|
156
|
+
const reconciled = await manager.reconcile();
|
|
157
|
+
if (reconciled.state === "approved") {
|
|
158
|
+
const saved = readPiVaultMode(this.#options.modeFile);
|
|
159
|
+
await this.useVault(
|
|
160
|
+
saved.ok && saved.record.mode === "custody" ? saved.record.routeId : void 0
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return reconciled;
|
|
164
|
+
} finally {
|
|
165
|
+
client.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Select the vault account used for requests after the operator approves
|
|
170
|
+
* enrollment. Await the host's Google credential-slot transition before
|
|
171
|
+
* returning, so provider registration cannot race it.
|
|
172
|
+
*/
|
|
173
|
+
async useVault(routeId) {
|
|
174
|
+
const enrollment = await this.#options.claustrum.readClaustrumEnrollmentStatus(
|
|
175
|
+
this.paths,
|
|
176
|
+
PI_ANTIGRAVITY_ENROLLMENT_NAME
|
|
177
|
+
);
|
|
178
|
+
if (enrollment.state !== "approved")
|
|
179
|
+
throw new PiVaultSetupError(
|
|
180
|
+
"Pi is not enrolled with the vault yet. Request access and wait for the operator to approve it first."
|
|
181
|
+
);
|
|
182
|
+
if (routeId !== void 0 && routeId.trim() === "")
|
|
183
|
+
throw new PiVaultSetupError("Choose a vault account to use.");
|
|
184
|
+
const record = routeId === void 0 ? { version: 1, mode: "custody" } : { version: 1, mode: "custody", routeId };
|
|
185
|
+
await writePiVaultMode(this.#options.modeFile, record);
|
|
186
|
+
await this.#options.onModeChanged?.(record);
|
|
187
|
+
return record;
|
|
188
|
+
}
|
|
189
|
+
/** Use Pi's local login only after vault enrollment has been disconnected. */
|
|
190
|
+
async useLocal() {
|
|
191
|
+
if (await enrollmentAuthority(
|
|
192
|
+
this.paths,
|
|
193
|
+
PI_ANTIGRAVITY_ENROLLMENT_NAME,
|
|
194
|
+
this.#options.claustrum
|
|
195
|
+
) !== "local") {
|
|
196
|
+
throw new PiVaultSetupError(
|
|
197
|
+
"Disconnect vault enrollment before using local accounts."
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
await writePiVaultMode(this.#options.modeFile, LOCAL_MODE);
|
|
201
|
+
await this.#options.onModeChanged?.(LOCAL_MODE);
|
|
202
|
+
return LOCAL_MODE;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Forget this host's enrollment (token and ceremony state) and go back to
|
|
206
|
+
* local mode. The vault keeps the enrollment approved until the operator
|
|
207
|
+
* revokes it there.
|
|
208
|
+
*/
|
|
209
|
+
async disconnect() {
|
|
210
|
+
const result = await this.#options.claustrum.disconnectClaustrumEnrollment(
|
|
211
|
+
this.paths,
|
|
212
|
+
PI_ANTIGRAVITY_ENROLLMENT_NAME
|
|
213
|
+
);
|
|
214
|
+
if (result === "busy")
|
|
215
|
+
throw new PiVaultSetupError(
|
|
216
|
+
"Vault enrollment is busy; disconnect did not change it."
|
|
217
|
+
);
|
|
218
|
+
await writePiVaultMode(this.#options.modeFile, LOCAL_MODE);
|
|
219
|
+
await this.#options.onModeChanged?.(LOCAL_MODE);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
function describeEnrollment(status) {
|
|
223
|
+
switch (status.state) {
|
|
224
|
+
case "idle":
|
|
225
|
+
return "not requested";
|
|
226
|
+
case "pending":
|
|
227
|
+
return status.retryCode ? `waiting for operator approval (retrying: ${status.retryCode})` : "waiting for operator approval";
|
|
228
|
+
case "approved":
|
|
229
|
+
return `approved as ${status.approvedName ?? status.proposedName}`;
|
|
230
|
+
case "denied":
|
|
231
|
+
return "denied by the operator";
|
|
232
|
+
case "blocked":
|
|
233
|
+
return `blocked (${status.code})`;
|
|
234
|
+
case "unavailable":
|
|
235
|
+
return `vault unavailable (${status.code})`;
|
|
236
|
+
case "busy":
|
|
237
|
+
return "another setup step is running";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function describeHostSlot(slot) {
|
|
241
|
+
switch (slot) {
|
|
242
|
+
case "login":
|
|
243
|
+
return "signed in with a Pi login";
|
|
244
|
+
case "placeholder":
|
|
245
|
+
return "holds the vault placeholder";
|
|
246
|
+
case "empty":
|
|
247
|
+
return "empty";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function formatPiVaultSetupStatus(status) {
|
|
251
|
+
const lines = [
|
|
252
|
+
`Mode: ${status.mode === "custody" ? "vault (custody)" : status.mode === "local" ? "local Pi login" : `unreadable (${status.modeProblem ?? "unknown problem"})`}`,
|
|
253
|
+
`Enrollment (${PI_ANTIGRAVITY_ENROLLMENT_NAME}): ${describeEnrollment(status.enrollment)}`,
|
|
254
|
+
status.hostSlot === "dormant" ? "Pi auth slot: dormant (not read)" : `Pi auth slot: ${describeHostSlot(status.hostSlot)}`
|
|
255
|
+
];
|
|
256
|
+
if (status.selectedRouteId !== void 0)
|
|
257
|
+
lines.push("Vault account: a specific account is chosen (marked below)");
|
|
258
|
+
if (status.mode === "local" && status.hostSlot === "placeholder")
|
|
259
|
+
lines.push(
|
|
260
|
+
"Conflict: local mode has no login to serve. Run /login or switch to the vault."
|
|
261
|
+
);
|
|
262
|
+
return lines;
|
|
263
|
+
}
|
|
264
|
+
var ITEM_ID_SALT = randomBytes(16);
|
|
265
|
+
function vaultAccountName(listed, account) {
|
|
266
|
+
return `Vault account ${listed.indexOf(account) + 1}`;
|
|
267
|
+
}
|
|
268
|
+
function opaqueItemId(routeId) {
|
|
269
|
+
return `vault-${createHash("sha256").update(ITEM_ID_SALT).update(routeId).digest("hex").slice(0, 16)}`;
|
|
270
|
+
}
|
|
271
|
+
function errorText(error) {
|
|
272
|
+
return error instanceof Error ? error.message : String(error);
|
|
273
|
+
}
|
|
274
|
+
function refused(error) {
|
|
275
|
+
if (error instanceof PiVaultSetupError)
|
|
276
|
+
return { ok: false, text: error.message, code: "vault-refused" };
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
async function waitForApproval(setup, wait = {}) {
|
|
280
|
+
const sleep = wait.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
281
|
+
const interval = wait.pollIntervalMs ?? 2e3;
|
|
282
|
+
const deadline = Date.now() + (wait.timeoutMs ?? 10 * 6e4);
|
|
283
|
+
let status = await setup.connectStep();
|
|
284
|
+
while (status.state === "pending" || status.state === "busy") {
|
|
285
|
+
if (Date.now() >= deadline) return status;
|
|
286
|
+
await sleep(interval);
|
|
287
|
+
status = await setup.connectStep();
|
|
288
|
+
}
|
|
289
|
+
return status;
|
|
290
|
+
}
|
|
291
|
+
function createPiVaultSection(options) {
|
|
292
|
+
return {
|
|
293
|
+
id: "vault",
|
|
294
|
+
title: "Vault",
|
|
295
|
+
build: async () => {
|
|
296
|
+
const setup = await options.setup();
|
|
297
|
+
const status = await setup.status();
|
|
298
|
+
const approved = status.enrollment.state === "approved";
|
|
299
|
+
const lines = formatPiVaultSetupStatus(status);
|
|
300
|
+
const items = [];
|
|
301
|
+
const port = approved ? options.accounts?.() : void 0;
|
|
302
|
+
if (port) {
|
|
303
|
+
try {
|
|
304
|
+
const listed = await port.list();
|
|
305
|
+
for (const account of listed) {
|
|
306
|
+
const name = vaultAccountName(listed, account);
|
|
307
|
+
const serving = status.mode === "custody" && status.selectedRouteId === account.routeId;
|
|
308
|
+
items.push({
|
|
309
|
+
id: opaqueItemId(account.routeId),
|
|
310
|
+
label: name,
|
|
311
|
+
detail: account.declined ? "declined on this host" : serving ? "serving Pi" : "available",
|
|
312
|
+
actions: account.declined ? [
|
|
313
|
+
{
|
|
314
|
+
id: "accept",
|
|
315
|
+
label: "Accept on this host",
|
|
316
|
+
run: async () => {
|
|
317
|
+
await port.accept(account.routeId);
|
|
318
|
+
return `${name} is accepted on this host.`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
] : [
|
|
322
|
+
{
|
|
323
|
+
id: "use",
|
|
324
|
+
label: "Serve Pi from this account",
|
|
325
|
+
run: async () => {
|
|
326
|
+
try {
|
|
327
|
+
await setup.useVault(account.routeId);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return refused(error);
|
|
330
|
+
}
|
|
331
|
+
return `Pi now serves Antigravity from ${name}.`;
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
id: "decline",
|
|
336
|
+
label: "Decline on this host",
|
|
337
|
+
description: "It stays in the vault and in this list, and is never sent from this host until accepted again.",
|
|
338
|
+
run: async () => {
|
|
339
|
+
await port.decline(account.routeId);
|
|
340
|
+
return `${name} is declined on this host.`;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
]
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
lines.push(`Vault accounts unavailable: ${errorText(error)}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const actions = [];
|
|
351
|
+
if (!approved)
|
|
352
|
+
actions.push({
|
|
353
|
+
id: "request-access",
|
|
354
|
+
label: "Request vault access",
|
|
355
|
+
description: `Asks the vault to enroll Pi as ${PI_ANTIGRAVITY_ENROLLMENT_NAME}; the operator approves it in the vault.`,
|
|
356
|
+
run: async ({ invocation }) => {
|
|
357
|
+
const step = await setup.connectStep();
|
|
358
|
+
if (step.state !== "pending" && step.state !== "busy")
|
|
359
|
+
return {
|
|
360
|
+
ok: step.state === "approved",
|
|
361
|
+
text: `Enrollment: ${describeEnrollment(step)}`
|
|
362
|
+
};
|
|
363
|
+
void waitForApproval(setup, options.wait).then((final) => {
|
|
364
|
+
invocation.notify(
|
|
365
|
+
`Enrollment: ${describeEnrollment(final)}`,
|
|
366
|
+
final.state === "approved" ? "info" : "warning"
|
|
367
|
+
);
|
|
368
|
+
}).catch((error) => {
|
|
369
|
+
invocation.notify(
|
|
370
|
+
`Requesting vault access failed: ${errorText(error)}`,
|
|
371
|
+
"error"
|
|
372
|
+
);
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
ok: true,
|
|
376
|
+
text: `Enrollment: ${describeEnrollment(step)}. You are told here when the operator decides.`
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
if (approved && status.mode !== "custody")
|
|
381
|
+
actions.push({
|
|
382
|
+
id: "use-vault",
|
|
383
|
+
label: "Serve Pi from the vault",
|
|
384
|
+
run: async () => {
|
|
385
|
+
try {
|
|
386
|
+
await setup.useVault(status.selectedRouteId);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
return refused(error);
|
|
389
|
+
}
|
|
390
|
+
return "Pi now serves Antigravity from the vault.";
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
if (status.mode !== "local" && (status.enrollment.state === "idle" || status.enrollment.state === "pending" || status.enrollment.state === "denied"))
|
|
394
|
+
actions.push({
|
|
395
|
+
id: "use-local",
|
|
396
|
+
label: "Serve Pi from its own login",
|
|
397
|
+
run: async () => {
|
|
398
|
+
await setup.useLocal();
|
|
399
|
+
return "Pi now serves Antigravity from its own login. Run /login if it has none.";
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
if (status.enrollment.state !== "idle")
|
|
403
|
+
actions.push({
|
|
404
|
+
id: "forget",
|
|
405
|
+
label: "Forget vault enrollment",
|
|
406
|
+
description: "Deletes Pi's enrollment token and returns to the local login. The operator can still revoke the enrollment in the vault.",
|
|
407
|
+
irreversible: true,
|
|
408
|
+
confirm: `Forget the ${PI_ANTIGRAVITY_ENROLLMENT_NAME} enrollment? Using the vault again needs a new approval.`,
|
|
409
|
+
run: async () => {
|
|
410
|
+
await setup.disconnect();
|
|
411
|
+
return "Vault enrollment forgotten; Pi serves Antigravity from its own login.";
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
return { lines, items, actions };
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/commands.ts
|
|
420
|
+
function registerPiAntigravityCommand(pi, options) {
|
|
421
|
+
const loadCommands = options.loadCommands ?? loadCommonAuthCommands;
|
|
422
|
+
pi.registerCommand(ANTIGRAVITY_MENU_COMMAND, {
|
|
423
|
+
description: "Google Antigravity: the account Pi serves, and the Claustrum vault",
|
|
424
|
+
handler: async (_args, ctx) => {
|
|
425
|
+
const commands = await loadCommands();
|
|
426
|
+
const menu = createAntigravityCommandMenu({
|
|
427
|
+
commands,
|
|
428
|
+
source: "sections",
|
|
429
|
+
sections: options.sections,
|
|
430
|
+
extras: options.extras
|
|
431
|
+
});
|
|
432
|
+
await commands.runPiCommandMenu(menu, ctx.ui, {
|
|
433
|
+
sessionId: ctx.sessionManager.getSessionId()
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function servingAccount(status, accounts) {
|
|
439
|
+
const available = accounts.filter((account) => !account.declined);
|
|
440
|
+
if (status.selectedRouteId !== void 0)
|
|
441
|
+
return available.find(
|
|
442
|
+
(account) => account.routeId === status.selectedRouteId
|
|
443
|
+
);
|
|
444
|
+
return available.length === 1 ? available[0] : void 0;
|
|
445
|
+
}
|
|
446
|
+
async function custodyAccounts(status, options) {
|
|
447
|
+
if (status.mode !== "custody" || status.enrollment.state !== "approved")
|
|
448
|
+
return { list: [] };
|
|
449
|
+
try {
|
|
450
|
+
return { list: await options.accounts().list() };
|
|
451
|
+
} catch (error) {
|
|
452
|
+
return {
|
|
453
|
+
list: [],
|
|
454
|
+
problem: `Vault accounts unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function localLoginLine(status) {
|
|
459
|
+
switch (status.hostSlot) {
|
|
460
|
+
case "dormant":
|
|
461
|
+
return "The native Pi login is dormant while vault enrollment controls serving.";
|
|
462
|
+
case "login":
|
|
463
|
+
return "Signed in with Pi's own login. Pi's /login replaces it and /logout removes it.";
|
|
464
|
+
case "placeholder":
|
|
465
|
+
return "Pi holds the vault placeholder, not a login. Run /login, or serve from the vault in the Vault section.";
|
|
466
|
+
case "empty":
|
|
467
|
+
return "Not signed in. Run Pi's /login to sign in.";
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function createPiAntigravityAccountSections(options) {
|
|
471
|
+
return {
|
|
472
|
+
accounts: {
|
|
473
|
+
title: "Accounts",
|
|
474
|
+
build: async () => {
|
|
475
|
+
const status = await (await options.setup()).status();
|
|
476
|
+
if (status.mode === "unreadable")
|
|
477
|
+
return {
|
|
478
|
+
lines: [
|
|
479
|
+
`The vault mode file is unreadable (${status.modeProblem ?? "unknown problem"}); Pi serves nothing until a mode is chosen in the Vault section.`
|
|
480
|
+
]
|
|
481
|
+
};
|
|
482
|
+
if (status.mode === "local")
|
|
483
|
+
return {
|
|
484
|
+
lines: [
|
|
485
|
+
"Pi serves Antigravity from its own login, one account, kept in Pi's auth store.",
|
|
486
|
+
localLoginLine(status)
|
|
487
|
+
]
|
|
488
|
+
};
|
|
489
|
+
const { list, problem } = await custodyAccounts(status, options);
|
|
490
|
+
const serving = servingAccount(status, list);
|
|
491
|
+
const items = list.map((account) => ({
|
|
492
|
+
id: opaqueItemId(account.routeId),
|
|
493
|
+
label: vaultAccountName(list, account),
|
|
494
|
+
detail: account.declined ? "declined on this host" : account === serving ? "serving Pi" : "available"
|
|
495
|
+
}));
|
|
496
|
+
return {
|
|
497
|
+
lines: [
|
|
498
|
+
"Pi serves Antigravity from the vault, one account at a time. Choose, decline or accept accounts in the Vault section.",
|
|
499
|
+
...status.hostSlot === "login" ? [
|
|
500
|
+
"Conflict: Pi also holds its own login. Requests are refused until /logout or a switch back to the local login."
|
|
501
|
+
] : [],
|
|
502
|
+
...problem ? [problem] : []
|
|
503
|
+
],
|
|
504
|
+
items
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
quota: {
|
|
509
|
+
title: "Quota",
|
|
510
|
+
build: () => ({
|
|
511
|
+
lines: [
|
|
512
|
+
"Pi does not record Antigravity quota. A request Google refuses for quota fails with Google's own error."
|
|
513
|
+
]
|
|
514
|
+
})
|
|
515
|
+
},
|
|
516
|
+
routing: {
|
|
517
|
+
title: "Routing",
|
|
518
|
+
build: async () => {
|
|
519
|
+
const status = await (await options.setup()).status();
|
|
520
|
+
if (status.mode === "unreadable")
|
|
521
|
+
return {
|
|
522
|
+
lines: [
|
|
523
|
+
"Vault enrollment authority is unreadable; requests are refused. Recover or Disconnect in the Vault section."
|
|
524
|
+
]
|
|
525
|
+
};
|
|
526
|
+
if (status.mode !== "custody")
|
|
527
|
+
return {
|
|
528
|
+
lines: [
|
|
529
|
+
"Every request uses Pi's own login. There is no account rotation."
|
|
530
|
+
]
|
|
531
|
+
};
|
|
532
|
+
const { list } = await custodyAccounts(status, options);
|
|
533
|
+
const serving = servingAccount(status, list);
|
|
534
|
+
return {
|
|
535
|
+
lines: [
|
|
536
|
+
serving ? `Every request uses ${vaultAccountName(list, serving)}, with a fresh token and project from the vault for each send. There is no account rotation.` : "No single vault account is chosen, so requests are refused. Choose one in the Vault section. There is no account rotation."
|
|
537
|
+
]
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
limits: {
|
|
542
|
+
title: "Limits",
|
|
543
|
+
build: () => ({
|
|
544
|
+
lines: [
|
|
545
|
+
"Pi has no per-account limits for Antigravity: with one account there is nothing to fall back to."
|
|
546
|
+
]
|
|
547
|
+
})
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
8
551
|
|
|
9
552
|
// src/credential-cache.ts
|
|
10
553
|
var packedRefreshByAccessToken = /* @__PURE__ */ new Map();
|
|
@@ -23,15 +566,40 @@ function getPackedRefresh(accessToken) {
|
|
|
23
566
|
return packedRefreshByAccessToken.get(accessToken);
|
|
24
567
|
}
|
|
25
568
|
|
|
569
|
+
// src/paths.ts
|
|
570
|
+
import { homedir } from "node:os";
|
|
571
|
+
import { dirname, join } from "node:path";
|
|
572
|
+
function getPiConfigDir() {
|
|
573
|
+
return process.env.PI_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent");
|
|
574
|
+
}
|
|
575
|
+
function getPiAntigravityAuthFile() {
|
|
576
|
+
return process.env.PI_ANTIGRAVITY_AUTH_FILE?.trim() || join(getPiConfigDir(), "antigravity-accounts.json");
|
|
577
|
+
}
|
|
578
|
+
function getPiAntigravityVaultDir() {
|
|
579
|
+
return join(dirname(getPiAntigravityAuthFile()), "antigravity-auth-vault");
|
|
580
|
+
}
|
|
581
|
+
function getPiAntigravityVaultModeFile() {
|
|
582
|
+
return join(getPiAntigravityVaultDir(), "pi-mode.json");
|
|
583
|
+
}
|
|
584
|
+
function getPiAntigravityVaultRosterFile() {
|
|
585
|
+
return join(getPiAntigravityVaultDir(), "pi-roster.json");
|
|
586
|
+
}
|
|
587
|
+
|
|
26
588
|
// src/stream.ts
|
|
27
589
|
import {
|
|
28
590
|
AgyRequestSessionStore,
|
|
29
591
|
ANTIGRAVITY_ENDPOINT,
|
|
592
|
+
applyAgyNativeGenerationConfig,
|
|
30
593
|
buildAgyAgentRequestMetadata,
|
|
31
594
|
buildAntigravityHarnessUserAgent,
|
|
32
595
|
ensureProjectContext,
|
|
33
596
|
fetchWithAgyCliTransport,
|
|
597
|
+
forwardAbort,
|
|
598
|
+
isRetiredAgyModel,
|
|
34
599
|
orderAgyRequestPayloadInPlace,
|
|
600
|
+
RETIRED_AGY_MODEL_MESSAGE,
|
|
601
|
+
redactCredentialError,
|
|
602
|
+
redactCredentialText,
|
|
35
603
|
resolveModelForHeaderStyle
|
|
36
604
|
} from "@cortexkit/antigravity-auth-core";
|
|
37
605
|
import {
|
|
@@ -41,6 +609,7 @@ import {
|
|
|
41
609
|
|
|
42
610
|
// src/convert.ts
|
|
43
611
|
import { toGeminiSchema } from "@cortexkit/antigravity-auth-core";
|
|
612
|
+
import * as piAi from "@earendil-works/pi-ai";
|
|
44
613
|
function sanitize(text) {
|
|
45
614
|
return text.replace(/[\uD800-\uDFFF]/gu, "\uFFFD");
|
|
46
615
|
}
|
|
@@ -109,6 +678,7 @@ function convertMessages(messages, options) {
|
|
|
109
678
|
}
|
|
110
679
|
for (const message of messages) {
|
|
111
680
|
if (!message) continue;
|
|
681
|
+
if (message.role === "system") continue;
|
|
112
682
|
if (message.role === "user") {
|
|
113
683
|
const parts = typeof message.content === "string" ? message.content.trim() ? [{ text: sanitize(message.content) }] : [] : convertUserParts(
|
|
114
684
|
message.content
|
|
@@ -158,15 +728,33 @@ function convertTools(tools) {
|
|
|
158
728
|
}
|
|
159
729
|
];
|
|
160
730
|
}
|
|
731
|
+
function resolveInstructionState(context) {
|
|
732
|
+
if (!context.messages.some((message) => message?.role === "system")) {
|
|
733
|
+
return {
|
|
734
|
+
systemPrompt: context.systemPrompt ?? "",
|
|
735
|
+
tools: context.tools ?? []
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
if (typeof piAi.getCurrentSystemPrompt !== "function" || typeof piAi.getCurrentTools !== "function") {
|
|
739
|
+
throw new Error(
|
|
740
|
+
"Pi transcript contains system messages but the installed @earendil-works/pi-ai does not export getCurrentSystemPrompt/getCurrentTools (requires >= 0.86)"
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
systemPrompt: piAi.getCurrentSystemPrompt(context.messages),
|
|
745
|
+
tools: piAi.getCurrentTools(context.messages)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
161
748
|
function buildGeminiRequest(context, options) {
|
|
162
749
|
const request = {
|
|
163
750
|
contents: convertMessages(context.messages, options)
|
|
164
751
|
};
|
|
165
|
-
const tools =
|
|
166
|
-
|
|
167
|
-
if (
|
|
752
|
+
const { systemPrompt, tools } = resolveInstructionState(context);
|
|
753
|
+
const geminiTools = convertTools(tools);
|
|
754
|
+
if (geminiTools) request.tools = geminiTools;
|
|
755
|
+
if (systemPrompt.trim()) {
|
|
168
756
|
request.systemInstruction = {
|
|
169
|
-
parts: [{ text: sanitize(
|
|
757
|
+
parts: [{ text: sanitize(systemPrompt) }]
|
|
170
758
|
};
|
|
171
759
|
}
|
|
172
760
|
return request;
|
|
@@ -327,7 +915,9 @@ function getRequestSessionKey(context, options) {
|
|
|
327
915
|
if (options?.sessionId) {
|
|
328
916
|
return options.sessionId;
|
|
329
917
|
}
|
|
330
|
-
const firstTimestamp = context.messages
|
|
918
|
+
const firstTimestamp = context.messages.find(
|
|
919
|
+
(message) => message.role !== "system"
|
|
920
|
+
)?.timestamp;
|
|
331
921
|
return firstTimestamp !== void 0 ? `message:${firstTimestamp}` : FALLBACK_SESSION_KEY;
|
|
332
922
|
}
|
|
333
923
|
function finalizePiAntigravityRequest(request, wireModel, scope) {
|
|
@@ -348,7 +938,7 @@ function finalizePiAntigravityRequest(request, wireModel, scope) {
|
|
|
348
938
|
}
|
|
349
939
|
function resolvePiAntigravityModel(model, reasoning) {
|
|
350
940
|
const lower = model.id.toLowerCase();
|
|
351
|
-
const supportsAgyTiers = model.reasoning && (lower.includes("gemini-3") || lower.includes("claude") && lower.includes("thinking"));
|
|
941
|
+
const supportsAgyTiers = model.reasoning && (lower.includes("gemini-3") || lower.includes("claude") && (lower.includes("thinking") || /claude-(opus|sonnet)-5-5/.test(lower)));
|
|
352
942
|
if (!supportsAgyTiers || !reasoning) {
|
|
353
943
|
return resolveModelForHeaderStyle(model.id, "antigravity");
|
|
354
944
|
}
|
|
@@ -356,23 +946,21 @@ function resolvePiAntigravityModel(model, reasoning) {
|
|
|
356
946
|
const baseModel = model.id.replace(/-(minimal|low|medium|high|xhigh)$/i, "");
|
|
357
947
|
return resolveModelForHeaderStyle(`${baseModel}-${tier}`, "antigravity");
|
|
358
948
|
}
|
|
359
|
-
|
|
949
|
+
function prepareAntigravityRequest(options) {
|
|
360
950
|
const resolved = resolvePiAntigravityModel(
|
|
361
951
|
options.model,
|
|
362
952
|
options.streamOptions?.reasoning
|
|
363
953
|
);
|
|
364
954
|
const wireModel = resolved.actualModel;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
model: options.model.id
|
|
375
|
-
});
|
|
955
|
+
if (isRetiredAgyModel(options.model.id) || isRetiredAgyModel(wireModel)) {
|
|
956
|
+
throw new Error(RETIRED_AGY_MODEL_MESSAGE);
|
|
957
|
+
}
|
|
958
|
+
const request = {
|
|
959
|
+
...buildGeminiRequest(options.context, {
|
|
960
|
+
provider: options.model.provider,
|
|
961
|
+
model: options.model.id
|
|
962
|
+
})
|
|
963
|
+
};
|
|
376
964
|
const generationConfig = {};
|
|
377
965
|
if (resolved.thinkingLevel) {
|
|
378
966
|
generationConfig.thinkingConfig = {
|
|
@@ -389,6 +977,7 @@ async function sendAntigravityRequest(options) {
|
|
|
389
977
|
if (typeof maxTokens === "number") {
|
|
390
978
|
generationConfig.maxOutputTokens = maxTokens;
|
|
391
979
|
}
|
|
980
|
+
applyAgyNativeGenerationConfig(wireModel, generationConfig);
|
|
392
981
|
if (Object.keys(generationConfig).length > 0) {
|
|
393
982
|
request.generationConfig = generationConfig;
|
|
394
983
|
}
|
|
@@ -398,11 +987,14 @@ async function sendAntigravityRequest(options) {
|
|
|
398
987
|
wireModel,
|
|
399
988
|
requestScope
|
|
400
989
|
);
|
|
990
|
+
return { wireModel, requestId, request };
|
|
991
|
+
}
|
|
992
|
+
function dispatchAntigravityRequest(prepared, credential, signal) {
|
|
401
993
|
const envelope = {
|
|
402
|
-
project:
|
|
403
|
-
requestId,
|
|
404
|
-
request,
|
|
405
|
-
model: wireModel,
|
|
994
|
+
project: credential.projectId,
|
|
995
|
+
requestId: prepared.requestId,
|
|
996
|
+
request: prepared.request,
|
|
997
|
+
model: prepared.wireModel,
|
|
406
998
|
userAgent: "antigravity",
|
|
407
999
|
requestType: "agent"
|
|
408
1000
|
};
|
|
@@ -412,39 +1004,113 @@ async function sendAntigravityRequest(options) {
|
|
|
412
1004
|
{
|
|
413
1005
|
method: "POST",
|
|
414
1006
|
headers: {
|
|
415
|
-
Authorization: `Bearer ${
|
|
1007
|
+
Authorization: `Bearer ${credential.accessToken}`,
|
|
416
1008
|
"Content-Type": "application/json",
|
|
417
1009
|
"User-Agent": buildAntigravityHarnessUserAgent(),
|
|
418
1010
|
"Accept-Encoding": "gzip"
|
|
419
1011
|
},
|
|
420
1012
|
body: JSON.stringify(envelope)
|
|
421
1013
|
},
|
|
422
|
-
{ signal
|
|
1014
|
+
{ signal }
|
|
423
1015
|
);
|
|
424
1016
|
}
|
|
425
|
-
function
|
|
1017
|
+
async function sendAntigravityRequest(options) {
|
|
1018
|
+
if (isRetiredAgyModel(options.model.id)) {
|
|
1019
|
+
throw new Error(RETIRED_AGY_MODEL_MESSAGE);
|
|
1020
|
+
}
|
|
1021
|
+
const packedRefresh = getPackedRefresh(options.accessToken) ?? "";
|
|
1022
|
+
const projectContext = await ensureProjectContext({
|
|
1023
|
+
type: "oauth",
|
|
1024
|
+
refresh: packedRefresh,
|
|
1025
|
+
access: options.accessToken,
|
|
1026
|
+
expires: Date.now() + 6e4
|
|
1027
|
+
});
|
|
1028
|
+
const prepared = prepareAntigravityRequest(options);
|
|
1029
|
+
await options.assertAdmission?.();
|
|
1030
|
+
return dispatchAntigravityRequest(
|
|
1031
|
+
prepared,
|
|
1032
|
+
{
|
|
1033
|
+
accessToken: options.accessToken,
|
|
1034
|
+
projectId: projectContext.effectiveProjectId
|
|
1035
|
+
},
|
|
1036
|
+
options.signal ?? options.streamOptions?.signal ?? null
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
function streamCortexKitAntigravity(model, context, options, assertAdmission) {
|
|
1040
|
+
return runAntigravityStream(
|
|
1041
|
+
model,
|
|
1042
|
+
context,
|
|
1043
|
+
options,
|
|
1044
|
+
async (sessionKey, signal) => {
|
|
1045
|
+
await assertAdmission?.();
|
|
1046
|
+
const accessToken = options?.apiKey ?? "";
|
|
1047
|
+
if (!accessToken)
|
|
1048
|
+
throw new Error("Missing Antigravity OAuth access token");
|
|
1049
|
+
return sendAntigravityRequest({
|
|
1050
|
+
model,
|
|
1051
|
+
context,
|
|
1052
|
+
streamOptions: options,
|
|
1053
|
+
accessToken,
|
|
1054
|
+
sessionKey,
|
|
1055
|
+
signal,
|
|
1056
|
+
assertAdmission
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
function createCortexKitAntigravityVaultStream(send, placeholderKey) {
|
|
1062
|
+
return (model, context, options) => runAntigravityStream(
|
|
1063
|
+
model,
|
|
1064
|
+
context,
|
|
1065
|
+
options,
|
|
1066
|
+
(sessionKey, signal, selectedCredentials) => {
|
|
1067
|
+
if (options?.apiKey !== placeholderKey)
|
|
1068
|
+
throw new Error(
|
|
1069
|
+
"The native Google credential slot is not synchronized for vault mode. No request was sent."
|
|
1070
|
+
);
|
|
1071
|
+
const prepared = prepareAntigravityRequest({
|
|
1072
|
+
model,
|
|
1073
|
+
context,
|
|
1074
|
+
streamOptions: options,
|
|
1075
|
+
sessionKey
|
|
1076
|
+
});
|
|
1077
|
+
return send(async (credential, sendSignal) => {
|
|
1078
|
+
selectedCredentials.add(credential.accessToken);
|
|
1079
|
+
try {
|
|
1080
|
+
return await dispatchAntigravityRequest(
|
|
1081
|
+
prepared,
|
|
1082
|
+
credential,
|
|
1083
|
+
sendSignal ?? signal
|
|
1084
|
+
);
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
throw redactCredentialError(error, [...selectedCredentials]);
|
|
1087
|
+
}
|
|
1088
|
+
}, signal);
|
|
1089
|
+
}
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
function runAntigravityStream(model, context, options, open) {
|
|
426
1093
|
const stream = createAssistantMessageEventStream();
|
|
427
1094
|
void (async () => {
|
|
428
1095
|
const output = createOutput(model);
|
|
1096
|
+
const selectedCredentials = new Set(
|
|
1097
|
+
options?.apiKey ? [options.apiKey] : []
|
|
1098
|
+
);
|
|
429
1099
|
stream.push({ type: "start", partial: output });
|
|
430
1100
|
let response;
|
|
431
1101
|
let requestAbort;
|
|
432
1102
|
let chunkIterator;
|
|
1103
|
+
let detachCaller = () => {
|
|
1104
|
+
};
|
|
433
1105
|
try {
|
|
434
|
-
const accessToken = options?.apiKey ?? "";
|
|
435
|
-
if (!accessToken)
|
|
436
|
-
throw new Error("Missing Antigravity OAuth access token");
|
|
437
1106
|
const sessionKey = getRequestSessionKey(context, options);
|
|
438
1107
|
requestAbort = new AbortController();
|
|
439
|
-
|
|
440
|
-
response = await
|
|
441
|
-
model,
|
|
442
|
-
context,
|
|
443
|
-
streamOptions: options,
|
|
444
|
-
accessToken,
|
|
1108
|
+
detachCaller = forwardAbort(options?.signal, requestAbort);
|
|
1109
|
+
response = await open(
|
|
445
1110
|
sessionKey,
|
|
446
|
-
signal
|
|
447
|
-
|
|
1111
|
+
requestAbort.signal,
|
|
1112
|
+
selectedCredentials
|
|
1113
|
+
);
|
|
448
1114
|
if (!response.ok) {
|
|
449
1115
|
throw new Error(
|
|
450
1116
|
`Antigravity request failed: HTTP ${response.status} ${await response.text()}`
|
|
@@ -641,16 +1307,221 @@ function streamCortexKitAntigravity(model, context, options) {
|
|
|
641
1307
|
await response?.body?.cancel().catch(() => {
|
|
642
1308
|
});
|
|
643
1309
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
644
|
-
output.errorMessage =
|
|
1310
|
+
output.errorMessage = redactCredentialText(
|
|
1311
|
+
error instanceof Error ? error.message : String(error),
|
|
1312
|
+
[...selectedCredentials]
|
|
1313
|
+
);
|
|
645
1314
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
646
1315
|
stream.end();
|
|
1316
|
+
} finally {
|
|
1317
|
+
detachCaller();
|
|
647
1318
|
}
|
|
648
1319
|
})();
|
|
649
1320
|
return stream;
|
|
650
1321
|
}
|
|
651
1322
|
|
|
1323
|
+
// src/vault-source.ts
|
|
1324
|
+
import { existsSync } from "node:fs";
|
|
1325
|
+
import {
|
|
1326
|
+
createAntigravityVaultAccountSource,
|
|
1327
|
+
enrollmentAuthority as enrollmentAuthority2,
|
|
1328
|
+
loadCommonAuthClaustrum
|
|
1329
|
+
} from "@cortexkit/antigravity-auth-core";
|
|
1330
|
+
var PiVaultSendError = class extends Error {
|
|
1331
|
+
constructor(message) {
|
|
1332
|
+
super(message);
|
|
1333
|
+
this.name = "PiVaultSendError";
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
function requireCustody(read) {
|
|
1337
|
+
if (!read.ok)
|
|
1338
|
+
throw new PiVaultSendError(
|
|
1339
|
+
`Antigravity vault mode is unclear (${read.reason}). Choose a mode in the Vault section of the Antigravity menu.`
|
|
1340
|
+
);
|
|
1341
|
+
if (read.record.mode !== "custody")
|
|
1342
|
+
throw new PiVaultSendError(
|
|
1343
|
+
"Antigravity is not served from the vault. Switch in the Vault section of the Antigravity menu."
|
|
1344
|
+
);
|
|
1345
|
+
return read.record;
|
|
1346
|
+
}
|
|
1347
|
+
function selectRoute(routes, routeId) {
|
|
1348
|
+
if (routeId !== void 0) {
|
|
1349
|
+
const selected = routes.find((route) => route.routeId === routeId);
|
|
1350
|
+
if (!selected)
|
|
1351
|
+
throw new PiVaultSendError(
|
|
1352
|
+
"The chosen vault account is no longer available (declined, removed or changed). Choose another in the Vault section of the Antigravity menu."
|
|
1353
|
+
);
|
|
1354
|
+
return selected;
|
|
1355
|
+
}
|
|
1356
|
+
const [only, ...rest] = routes;
|
|
1357
|
+
if (!only)
|
|
1358
|
+
throw new PiVaultSendError(
|
|
1359
|
+
"The vault serves Pi no Antigravity accounts yet."
|
|
1360
|
+
);
|
|
1361
|
+
if (rest.length > 0)
|
|
1362
|
+
throw new PiVaultSendError(
|
|
1363
|
+
"The vault serves Pi several Antigravity accounts. Choose one in the Vault section of the Antigravity menu."
|
|
1364
|
+
);
|
|
1365
|
+
return only;
|
|
1366
|
+
}
|
|
1367
|
+
async function openCustody(options) {
|
|
1368
|
+
const record = requireCustody(await options.readMode());
|
|
1369
|
+
const source = await options.source();
|
|
1370
|
+
return { record, source };
|
|
1371
|
+
}
|
|
1372
|
+
function createPiVaultSend(options) {
|
|
1373
|
+
return async (dispatch, signal) => {
|
|
1374
|
+
const { record, source } = await openCustody(options);
|
|
1375
|
+
signal.throwIfAborted();
|
|
1376
|
+
let routes = source.routes();
|
|
1377
|
+
if (routes.length === 0) {
|
|
1378
|
+
await source.refresh();
|
|
1379
|
+
signal.throwIfAborted();
|
|
1380
|
+
routes = source.routes();
|
|
1381
|
+
}
|
|
1382
|
+
const ref = selectRoute(routes, record.routeId);
|
|
1383
|
+
return source.send(ref, dispatch, { site: "model", signal });
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
function createPiVaultAccountsPort(options) {
|
|
1387
|
+
let lastRows = /* @__PURE__ */ new Map();
|
|
1388
|
+
const refFor = (routeId) => {
|
|
1389
|
+
const ref = lastRows.get(routeId);
|
|
1390
|
+
if (!ref)
|
|
1391
|
+
throw new PiVaultSendError(
|
|
1392
|
+
"That vault account is no longer listed. Open the Vault section again."
|
|
1393
|
+
);
|
|
1394
|
+
return ref;
|
|
1395
|
+
};
|
|
1396
|
+
return {
|
|
1397
|
+
list: async () => {
|
|
1398
|
+
const { source } = await openCustody(options);
|
|
1399
|
+
const roster = await source.refresh();
|
|
1400
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1401
|
+
const choices = [];
|
|
1402
|
+
for (const row of roster?.rows ?? []) {
|
|
1403
|
+
if (!row.accountIdentity) continue;
|
|
1404
|
+
const ref = {
|
|
1405
|
+
routeId: row.routeId,
|
|
1406
|
+
credentialId: row.credentialId,
|
|
1407
|
+
accountIdentity: row.accountIdentity,
|
|
1408
|
+
label: row.label,
|
|
1409
|
+
...row.email !== void 0 && { email: row.email }
|
|
1410
|
+
};
|
|
1411
|
+
rows.set(row.routeId, ref);
|
|
1412
|
+
const declined = (roster?.declined ?? []).some(
|
|
1413
|
+
(entry) => entry.credentialId === row.credentialId && (entry.accountIdentity === void 0 || entry.accountIdentity === row.accountIdentity)
|
|
1414
|
+
);
|
|
1415
|
+
choices.push({
|
|
1416
|
+
routeId: row.routeId,
|
|
1417
|
+
label: row.label,
|
|
1418
|
+
...row.email !== void 0 && { email: row.email },
|
|
1419
|
+
declined
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
lastRows = rows;
|
|
1423
|
+
return choices;
|
|
1424
|
+
},
|
|
1425
|
+
decline: async (routeId) => {
|
|
1426
|
+
const { source } = await openCustody(options);
|
|
1427
|
+
await source.decline(refFor(routeId));
|
|
1428
|
+
},
|
|
1429
|
+
accept: async (routeId) => {
|
|
1430
|
+
const { source } = await openCustody(options);
|
|
1431
|
+
await source.accept(refFor(routeId));
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
async function claustrumConnectionFile() {
|
|
1436
|
+
const { resolveClaustrumConnectionPath } = await import("@cortexkit/claustrum-client");
|
|
1437
|
+
return resolveClaustrumConnectionPath();
|
|
1438
|
+
}
|
|
1439
|
+
function memoize(load) {
|
|
1440
|
+
let pending;
|
|
1441
|
+
return () => {
|
|
1442
|
+
pending ??= load().catch((error) => {
|
|
1443
|
+
pending = void 0;
|
|
1444
|
+
throw error;
|
|
1445
|
+
});
|
|
1446
|
+
return pending;
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
function createPiVaultRuntime(readHostSlot, seams = {}, onModeChanged) {
|
|
1450
|
+
const vaultDir = seams.vaultDir ?? getPiAntigravityVaultDir();
|
|
1451
|
+
const modeFile = seams.modeFile ?? getPiAntigravityVaultModeFile();
|
|
1452
|
+
const rosterFile = seams.rosterFile ?? getPiAntigravityVaultRosterFile();
|
|
1453
|
+
const loadClaustrum = seams.loadClaustrum ?? loadCommonAuthClaustrum;
|
|
1454
|
+
const readMode = () => readPiVaultMode(modeFile);
|
|
1455
|
+
const source = memoize(async () => {
|
|
1456
|
+
const claustrum = await loadClaustrum();
|
|
1457
|
+
const { tokenPath } = claustrum.hostEnrollmentPaths({
|
|
1458
|
+
stateDir: vaultDir,
|
|
1459
|
+
host: PI_ANTIGRAVITY_VAULT_HOST
|
|
1460
|
+
});
|
|
1461
|
+
return createAntigravityVaultAccountSource({
|
|
1462
|
+
claustrum,
|
|
1463
|
+
host: PI_ANTIGRAVITY_VAULT_HOST,
|
|
1464
|
+
rosterPath: rosterFile,
|
|
1465
|
+
tokenPath,
|
|
1466
|
+
connect: seams.connect ?? (async () => claustrum.connectClaustrumScopedClient({
|
|
1467
|
+
connectionFile: await claustrumConnectionFile()
|
|
1468
|
+
})),
|
|
1469
|
+
// The vault serves this host only once setup has stored an approved
|
|
1470
|
+
// enrollment token; Pi's own send path also requires custody mode.
|
|
1471
|
+
isCustodyActive: () => existsSync(tokenPath),
|
|
1472
|
+
// Pi reads the HTTP status of each send itself.
|
|
1473
|
+
reporterSource: "direct"
|
|
1474
|
+
});
|
|
1475
|
+
});
|
|
1476
|
+
const setup = memoize(async () => {
|
|
1477
|
+
const claustrum = await loadClaustrum();
|
|
1478
|
+
return new PiVaultSetup({
|
|
1479
|
+
claustrum,
|
|
1480
|
+
stateDir: vaultDir,
|
|
1481
|
+
modeFile,
|
|
1482
|
+
readHostSlot,
|
|
1483
|
+
...onModeChanged && { onModeChanged },
|
|
1484
|
+
connectEnrollment: seams.connectEnrollment ?? (async () => claustrum.connectClaustrumEnrollmentClient({
|
|
1485
|
+
connectionFile: await claustrumConnectionFile()
|
|
1486
|
+
}))
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
return {
|
|
1490
|
+
readMode,
|
|
1491
|
+
setup,
|
|
1492
|
+
requestPath: {
|
|
1493
|
+
readMode: async () => {
|
|
1494
|
+
const settings = await setup();
|
|
1495
|
+
const status = await settings.sourceStatus();
|
|
1496
|
+
const authority = await enrollmentAuthority2(
|
|
1497
|
+
settings.paths,
|
|
1498
|
+
PI_ANTIGRAVITY_ENROLLMENT_NAME
|
|
1499
|
+
);
|
|
1500
|
+
if (authority === "local" && status.mode === "custody")
|
|
1501
|
+
throw new PiVaultSendError(
|
|
1502
|
+
"Vault enrollment is not active. No request was sent."
|
|
1503
|
+
);
|
|
1504
|
+
if (status.mode === "unreadable")
|
|
1505
|
+
throw new PiVaultSendError(
|
|
1506
|
+
"Vault enrollment or source settings cannot be read."
|
|
1507
|
+
);
|
|
1508
|
+
return {
|
|
1509
|
+
ok: true,
|
|
1510
|
+
record: {
|
|
1511
|
+
version: 1,
|
|
1512
|
+
mode: status.mode,
|
|
1513
|
+
...status.selectedRouteId ? { routeId: status.selectedRouteId } : {}
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
},
|
|
1517
|
+
source
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
652
1522
|
// src/index.ts
|
|
653
|
-
var
|
|
1523
|
+
var CUSTODY_PLACEHOLDER_REFRESH_PREFIX = "claustrum-tombstone:v1:";
|
|
1524
|
+
var CUSTODY_PROVIDER_KEY_MARKER = "antigravity-vault-custody";
|
|
654
1525
|
function textImageInput() {
|
|
655
1526
|
return ["text", "image"];
|
|
656
1527
|
}
|
|
@@ -682,6 +1553,10 @@ async function loginAntigravity(callbacks) {
|
|
|
682
1553
|
};
|
|
683
1554
|
}
|
|
684
1555
|
async function refreshAntigravityCredentials(credentials) {
|
|
1556
|
+
if (credentials.refresh.startsWith(CUSTODY_PLACEHOLDER_REFRESH_PREFIX))
|
|
1557
|
+
throw new Error(
|
|
1558
|
+
"Pi holds the Antigravity vault placeholder, not a login. Run /login, or serve Antigravity from the vault."
|
|
1559
|
+
);
|
|
685
1560
|
const refreshToken = credentials.refresh.split("|")[0] ?? credentials.refresh;
|
|
686
1561
|
const refreshed = await refreshAntigravityToken(refreshToken);
|
|
687
1562
|
const projectSegments = credentials.refresh.includes("|") ? credentials.refresh.slice(credentials.refresh.indexOf("|")) : "";
|
|
@@ -691,8 +1566,8 @@ async function refreshAntigravityCredentials(credentials) {
|
|
|
691
1566
|
expires: refreshed.expires
|
|
692
1567
|
};
|
|
693
1568
|
}
|
|
694
|
-
function
|
|
695
|
-
|
|
1569
|
+
function publicModels() {
|
|
1570
|
+
return Object.values(getPublicModelDefinitions()).filter((model) => !model.modalities.output.includes("image")).map((model) => ({
|
|
696
1571
|
id: model.id,
|
|
697
1572
|
name: model.name,
|
|
698
1573
|
reasoning: model.reasoning,
|
|
@@ -701,24 +1576,187 @@ function cortexKitPiAntigravityAuth(pi) {
|
|
|
701
1576
|
contextWindow: model.limit.context,
|
|
702
1577
|
maxTokens: model.limit.output
|
|
703
1578
|
}));
|
|
704
|
-
|
|
1579
|
+
}
|
|
1580
|
+
function localProviderConfig(models, assertLocalAdmission) {
|
|
1581
|
+
return {
|
|
705
1582
|
name: "Google Antigravity (CortexKit OAuth)",
|
|
706
1583
|
baseUrl: "https://cloudcode-pa.googleapis.com",
|
|
707
1584
|
api: "google-generative-ai",
|
|
708
1585
|
models,
|
|
709
1586
|
oauth: {
|
|
710
1587
|
name: "Google Antigravity (CortexKit)",
|
|
711
|
-
login:
|
|
712
|
-
|
|
1588
|
+
login: async (callbacks) => {
|
|
1589
|
+
await assertLocalAdmission();
|
|
1590
|
+
return loginAntigravity(callbacks);
|
|
1591
|
+
},
|
|
1592
|
+
refreshToken: async (credentials) => {
|
|
1593
|
+
await assertLocalAdmission();
|
|
1594
|
+
return refreshAntigravityCredentials(credentials);
|
|
1595
|
+
},
|
|
713
1596
|
getApiKey: (credentials) => {
|
|
714
1597
|
rememberPackedRefresh(credentials.access, credentials.refresh);
|
|
715
1598
|
return credentials.access;
|
|
716
1599
|
}
|
|
717
1600
|
},
|
|
718
|
-
streamSimple: streamCortexKitAntigravity
|
|
1601
|
+
streamSimple: (model, context, options) => streamCortexKitAntigravity(model, context, options, assertLocalAdmission)
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
function custodyProviderConfig(models, send) {
|
|
1605
|
+
return {
|
|
1606
|
+
name: "Google Antigravity (CortexKit vault)",
|
|
1607
|
+
baseUrl: "https://cloudcode-pa.googleapis.com",
|
|
1608
|
+
api: "google-generative-ai",
|
|
1609
|
+
models,
|
|
1610
|
+
apiKey: CUSTODY_PROVIDER_KEY_MARKER,
|
|
1611
|
+
streamSimple: createCortexKitAntigravityVaultStream(
|
|
1612
|
+
send,
|
|
1613
|
+
CUSTODY_PROVIDER_KEY_MARKER
|
|
1614
|
+
)
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function refusingProviderConfig(models, reason) {
|
|
1618
|
+
return {
|
|
1619
|
+
name: "Google Antigravity (CortexKit, unavailable)",
|
|
1620
|
+
baseUrl: "https://cloudcode-pa.googleapis.com",
|
|
1621
|
+
api: "google-generative-ai",
|
|
1622
|
+
models,
|
|
1623
|
+
apiKey: CUSTODY_PROVIDER_KEY_MARKER,
|
|
1624
|
+
streamSimple: createCortexKitAntigravityVaultStream(async () => {
|
|
1625
|
+
throw new PiVaultSetupError(
|
|
1626
|
+
`${reason} Resolve it from /antigravity; Antigravity requests are refused until then. No request was sent.`
|
|
1627
|
+
);
|
|
1628
|
+
}, CUSTODY_PROVIDER_KEY_MARKER)
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
async function registerCortexKitPiAntigravity(pi, seams = {}) {
|
|
1632
|
+
const models = publicModels();
|
|
1633
|
+
const readHostSlot = seams.readHostSlot ?? (() => readStoredCredential(ANTIGRAVITY_PROVIDER_ID2));
|
|
1634
|
+
let refreshRegistry;
|
|
1635
|
+
const vault = createPiVaultRuntime(readHostSlot, seams, async () => {
|
|
1636
|
+
try {
|
|
1637
|
+
await synchronizeSlot();
|
|
1638
|
+
} finally {
|
|
1639
|
+
refreshRegistry?.();
|
|
1640
|
+
}
|
|
719
1641
|
});
|
|
1642
|
+
const send = createPiVaultSend(vault.requestPath);
|
|
1643
|
+
const register = (mode) => {
|
|
1644
|
+
const local = mode.ok && mode.record.mode === "local";
|
|
1645
|
+
pi.registerProvider(
|
|
1646
|
+
ANTIGRAVITY_PROVIDER_ID2,
|
|
1647
|
+
local ? localProviderConfig(models, assertLocalAdmission) : custodyProviderConfig(models, send)
|
|
1648
|
+
);
|
|
1649
|
+
};
|
|
1650
|
+
const currentMode = async () => {
|
|
1651
|
+
const setup = await vault.setup();
|
|
1652
|
+
const status = await setup.sourceStatus();
|
|
1653
|
+
const authority = await enrollmentAuthority3(
|
|
1654
|
+
setup.paths,
|
|
1655
|
+
PI_ANTIGRAVITY_ENROLLMENT_NAME
|
|
1656
|
+
);
|
|
1657
|
+
if (authority === "unreadable" || status.mode === "unreadable") {
|
|
1658
|
+
throw new PiVaultSetupError(
|
|
1659
|
+
"Vault enrollment or source settings cannot be read. Local credentials remain unavailable."
|
|
1660
|
+
);
|
|
1661
|
+
}
|
|
1662
|
+
if (authority === "vault")
|
|
1663
|
+
return {
|
|
1664
|
+
version: 1,
|
|
1665
|
+
mode: "custody",
|
|
1666
|
+
...status.selectedRouteId ? { routeId: status.selectedRouteId } : {}
|
|
1667
|
+
};
|
|
1668
|
+
if (status.mode !== "local")
|
|
1669
|
+
throw new PiVaultSetupError(
|
|
1670
|
+
"Vault enrollment must be approved before activating vault credentials."
|
|
1671
|
+
);
|
|
1672
|
+
return { version: 1, mode: "local" };
|
|
1673
|
+
};
|
|
1674
|
+
const assertLocalAdmission = async () => {
|
|
1675
|
+
if ((await currentMode()).mode !== "local")
|
|
1676
|
+
throw new PiVaultSetupError(
|
|
1677
|
+
"Vault enrollment excludes local login, refresh and request execution."
|
|
1678
|
+
);
|
|
1679
|
+
};
|
|
1680
|
+
const synchronizeSlotOnce = async () => {
|
|
1681
|
+
try {
|
|
1682
|
+
await swapSlotForMode();
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
pi.registerProvider(
|
|
1685
|
+
ANTIGRAVITY_PROVIDER_ID2,
|
|
1686
|
+
refusingProviderConfig(
|
|
1687
|
+
models,
|
|
1688
|
+
error instanceof Error ? error.message : String(error)
|
|
1689
|
+
)
|
|
1690
|
+
);
|
|
1691
|
+
throw error;
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
let synchronizing = Promise.resolve();
|
|
1695
|
+
const synchronizeSlot = () => {
|
|
1696
|
+
const run = synchronizing.then(synchronizeSlotOnce);
|
|
1697
|
+
synchronizing = run.catch(() => {
|
|
1698
|
+
});
|
|
1699
|
+
return run;
|
|
1700
|
+
};
|
|
1701
|
+
const swapSlotForMode = async () => {
|
|
1702
|
+
const mode = await currentMode();
|
|
1703
|
+
const { openPiSlot } = await (seams.loadPiSlot ?? loadCommonAuthPiSlot)();
|
|
1704
|
+
const slot = openPiSlot({
|
|
1705
|
+
authPath: seams.nativeAuthPath ?? join2(getAgentDir(), "auth.json"),
|
|
1706
|
+
stashPath: seams.nativeStashPath ?? join2(
|
|
1707
|
+
seams.vaultDir ?? getPiAntigravityVaultDir(),
|
|
1708
|
+
"google-login-stash.json"
|
|
1709
|
+
),
|
|
1710
|
+
provider: ANTIGRAVITY_PROVIDER_ID2,
|
|
1711
|
+
placeholderKey: CUSTODY_PROVIDER_KEY_MARKER
|
|
1712
|
+
});
|
|
1713
|
+
if (mode.mode === "custody") await slot.enterVault();
|
|
1714
|
+
else if (await slot.exitVault() === "conflict")
|
|
1715
|
+
throw new PiVaultSetupError(
|
|
1716
|
+
"The native Google login changed. It was not overwritten; restore the saved login after resolving the conflict."
|
|
1717
|
+
);
|
|
1718
|
+
const after = await currentMode();
|
|
1719
|
+
if (after.mode !== mode.mode)
|
|
1720
|
+
throw new PiVaultSetupError(
|
|
1721
|
+
"Vault enrollment changed while the credential slot was being synchronized. The account source was not activated."
|
|
1722
|
+
);
|
|
1723
|
+
register({ ok: true, record: after });
|
|
1724
|
+
};
|
|
1725
|
+
await synchronizeSlot().catch(() => {
|
|
1726
|
+
});
|
|
1727
|
+
pi.on?.("session_start", async (_event, context) => {
|
|
1728
|
+
refreshRegistry = () => context.modelRegistry.refresh();
|
|
1729
|
+
try {
|
|
1730
|
+
await synchronizeSlot();
|
|
1731
|
+
} finally {
|
|
1732
|
+
refreshRegistry();
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
const accounts = createPiVaultAccountsPort(vault.requestPath);
|
|
1736
|
+
const extension = {
|
|
1737
|
+
accountSections: createPiAntigravityAccountSections({
|
|
1738
|
+
setup: vault.setup,
|
|
1739
|
+
accounts: () => accounts
|
|
1740
|
+
}),
|
|
1741
|
+
vaultSection: createPiVaultSection({
|
|
1742
|
+
setup: vault.setup,
|
|
1743
|
+
accounts: () => accounts
|
|
1744
|
+
})
|
|
1745
|
+
};
|
|
1746
|
+
registerPiAntigravityCommand(pi, {
|
|
1747
|
+
sections: extension.accountSections,
|
|
1748
|
+
extras: [extension.vaultSection],
|
|
1749
|
+
...seams.loadCommands && { loadCommands: seams.loadCommands }
|
|
1750
|
+
});
|
|
1751
|
+
return extension;
|
|
1752
|
+
}
|
|
1753
|
+
async function cortexKitPiAntigravityAuth(pi) {
|
|
1754
|
+
await registerCortexKitPiAntigravity(pi);
|
|
720
1755
|
}
|
|
721
1756
|
export {
|
|
722
|
-
|
|
1757
|
+
CUSTODY_PLACEHOLDER_REFRESH_PREFIX,
|
|
1758
|
+
CUSTODY_PROVIDER_KEY_MARKER,
|
|
1759
|
+
cortexKitPiAntigravityAuth as default,
|
|
1760
|
+
registerCortexKitPiAntigravity
|
|
723
1761
|
};
|
|
724
1762
|
//# sourceMappingURL=index.js.map
|