@cortexkit/pi-antigravity-auth 2.2.1 → 3.0.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/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,6 +566,25 @@ 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,
@@ -31,7 +593,10 @@ import {
31
593
  buildAntigravityHarnessUserAgent,
32
594
  ensureProjectContext,
33
595
  fetchWithAgyCliTransport,
596
+ forwardAbort,
34
597
  orderAgyRequestPayloadInPlace,
598
+ redactCredentialError,
599
+ redactCredentialText,
35
600
  resolveModelForHeaderStyle
36
601
  } from "@cortexkit/antigravity-auth-core";
37
602
  import {
@@ -41,6 +606,7 @@ import {
41
606
 
42
607
  // src/convert.ts
43
608
  import { toGeminiSchema } from "@cortexkit/antigravity-auth-core";
609
+ import * as piAi from "@earendil-works/pi-ai";
44
610
  function sanitize(text) {
45
611
  return text.replace(/[\uD800-\uDFFF]/gu, "\uFFFD");
46
612
  }
@@ -109,6 +675,7 @@ function convertMessages(messages, options) {
109
675
  }
110
676
  for (const message of messages) {
111
677
  if (!message) continue;
678
+ if (message.role === "system") continue;
112
679
  if (message.role === "user") {
113
680
  const parts = typeof message.content === "string" ? message.content.trim() ? [{ text: sanitize(message.content) }] : [] : convertUserParts(
114
681
  message.content
@@ -158,15 +725,33 @@ function convertTools(tools) {
158
725
  }
159
726
  ];
160
727
  }
728
+ function resolveInstructionState(context) {
729
+ if (!context.messages.some((message) => message?.role === "system")) {
730
+ return {
731
+ systemPrompt: context.systemPrompt ?? "",
732
+ tools: context.tools ?? []
733
+ };
734
+ }
735
+ if (typeof piAi.getCurrentSystemPrompt !== "function" || typeof piAi.getCurrentTools !== "function") {
736
+ throw new Error(
737
+ "Pi transcript contains system messages but the installed @earendil-works/pi-ai does not export getCurrentSystemPrompt/getCurrentTools (requires >= 0.86)"
738
+ );
739
+ }
740
+ return {
741
+ systemPrompt: piAi.getCurrentSystemPrompt(context.messages),
742
+ tools: piAi.getCurrentTools(context.messages)
743
+ };
744
+ }
161
745
  function buildGeminiRequest(context, options) {
162
746
  const request = {
163
747
  contents: convertMessages(context.messages, options)
164
748
  };
165
- const tools = convertTools(context.tools);
166
- if (tools) request.tools = tools;
167
- if (context.systemPrompt?.trim()) {
749
+ const { systemPrompt, tools } = resolveInstructionState(context);
750
+ const geminiTools = convertTools(tools);
751
+ if (geminiTools) request.tools = geminiTools;
752
+ if (systemPrompt.trim()) {
168
753
  request.systemInstruction = {
169
- parts: [{ text: sanitize(context.systemPrompt) }]
754
+ parts: [{ text: sanitize(systemPrompt) }]
170
755
  };
171
756
  }
172
757
  return request;
@@ -327,7 +912,9 @@ function getRequestSessionKey(context, options) {
327
912
  if (options?.sessionId) {
328
913
  return options.sessionId;
329
914
  }
330
- const firstTimestamp = context.messages[0]?.timestamp;
915
+ const firstTimestamp = context.messages.find(
916
+ (message) => message.role !== "system"
917
+ )?.timestamp;
331
918
  return firstTimestamp !== void 0 ? `message:${firstTimestamp}` : FALLBACK_SESSION_KEY;
332
919
  }
333
920
  function finalizePiAntigravityRequest(request, wireModel, scope) {
@@ -356,19 +943,12 @@ function resolvePiAntigravityModel(model, reasoning) {
356
943
  const baseModel = model.id.replace(/-(minimal|low|medium|high|xhigh)$/i, "");
357
944
  return resolveModelForHeaderStyle(`${baseModel}-${tier}`, "antigravity");
358
945
  }
359
- async function sendAntigravityRequest(options) {
946
+ function prepareAntigravityRequest(options) {
360
947
  const resolved = resolvePiAntigravityModel(
361
948
  options.model,
362
949
  options.streamOptions?.reasoning
363
950
  );
364
951
  const wireModel = resolved.actualModel;
365
- const packedRefresh = getPackedRefresh(options.accessToken) ?? "";
366
- const projectContext = await ensureProjectContext({
367
- type: "oauth",
368
- refresh: packedRefresh,
369
- access: options.accessToken,
370
- expires: Date.now() + 6e4
371
- });
372
952
  const request = buildGeminiRequest(options.context, {
373
953
  provider: options.model.provider,
374
954
  model: options.model.id
@@ -398,11 +978,14 @@ async function sendAntigravityRequest(options) {
398
978
  wireModel,
399
979
  requestScope
400
980
  );
981
+ return { wireModel, requestId, request };
982
+ }
983
+ function dispatchAntigravityRequest(prepared, credential, signal) {
401
984
  const envelope = {
402
- project: projectContext.effectiveProjectId,
403
- requestId,
404
- request,
405
- model: wireModel,
985
+ project: credential.projectId,
986
+ requestId: prepared.requestId,
987
+ request: prepared.request,
988
+ model: prepared.wireModel,
406
989
  userAgent: "antigravity",
407
990
  requestType: "agent"
408
991
  };
@@ -412,39 +995,110 @@ async function sendAntigravityRequest(options) {
412
995
  {
413
996
  method: "POST",
414
997
  headers: {
415
- Authorization: `Bearer ${options.accessToken}`,
998
+ Authorization: `Bearer ${credential.accessToken}`,
416
999
  "Content-Type": "application/json",
417
1000
  "User-Agent": buildAntigravityHarnessUserAgent(),
418
1001
  "Accept-Encoding": "gzip"
419
1002
  },
420
1003
  body: JSON.stringify(envelope)
421
1004
  },
422
- { signal: options.signal ?? options.streamOptions?.signal ?? null }
1005
+ { signal }
1006
+ );
1007
+ }
1008
+ async function sendAntigravityRequest(options) {
1009
+ const packedRefresh = getPackedRefresh(options.accessToken) ?? "";
1010
+ const projectContext = await ensureProjectContext({
1011
+ type: "oauth",
1012
+ refresh: packedRefresh,
1013
+ access: options.accessToken,
1014
+ expires: Date.now() + 6e4
1015
+ });
1016
+ const prepared = prepareAntigravityRequest(options);
1017
+ await options.assertAdmission?.();
1018
+ return dispatchAntigravityRequest(
1019
+ prepared,
1020
+ {
1021
+ accessToken: options.accessToken,
1022
+ projectId: projectContext.effectiveProjectId
1023
+ },
1024
+ options.signal ?? options.streamOptions?.signal ?? null
1025
+ );
1026
+ }
1027
+ function streamCortexKitAntigravity(model, context, options, assertAdmission) {
1028
+ return runAntigravityStream(
1029
+ model,
1030
+ context,
1031
+ options,
1032
+ async (sessionKey, signal) => {
1033
+ await assertAdmission?.();
1034
+ const accessToken = options?.apiKey ?? "";
1035
+ if (!accessToken)
1036
+ throw new Error("Missing Antigravity OAuth access token");
1037
+ return sendAntigravityRequest({
1038
+ model,
1039
+ context,
1040
+ streamOptions: options,
1041
+ accessToken,
1042
+ sessionKey,
1043
+ signal,
1044
+ assertAdmission
1045
+ });
1046
+ }
1047
+ );
1048
+ }
1049
+ function createCortexKitAntigravityVaultStream(send, placeholderKey) {
1050
+ return (model, context, options) => runAntigravityStream(
1051
+ model,
1052
+ context,
1053
+ options,
1054
+ (sessionKey, signal, selectedCredentials) => {
1055
+ if (options?.apiKey !== placeholderKey)
1056
+ throw new Error(
1057
+ "The native Google credential slot is not synchronized for vault mode. No request was sent."
1058
+ );
1059
+ const prepared = prepareAntigravityRequest({
1060
+ model,
1061
+ context,
1062
+ streamOptions: options,
1063
+ sessionKey
1064
+ });
1065
+ return send(async (credential, sendSignal) => {
1066
+ selectedCredentials.add(credential.accessToken);
1067
+ try {
1068
+ return await dispatchAntigravityRequest(
1069
+ prepared,
1070
+ credential,
1071
+ sendSignal ?? signal
1072
+ );
1073
+ } catch (error) {
1074
+ throw redactCredentialError(error, [...selectedCredentials]);
1075
+ }
1076
+ }, signal);
1077
+ }
423
1078
  );
424
1079
  }
425
- function streamCortexKitAntigravity(model, context, options) {
1080
+ function runAntigravityStream(model, context, options, open) {
426
1081
  const stream = createAssistantMessageEventStream();
427
1082
  void (async () => {
428
1083
  const output = createOutput(model);
1084
+ const selectedCredentials = new Set(
1085
+ options?.apiKey ? [options.apiKey] : []
1086
+ );
429
1087
  stream.push({ type: "start", partial: output });
430
1088
  let response;
431
1089
  let requestAbort;
432
1090
  let chunkIterator;
1091
+ let detachCaller = () => {
1092
+ };
433
1093
  try {
434
- const accessToken = options?.apiKey ?? "";
435
- if (!accessToken)
436
- throw new Error("Missing Antigravity OAuth access token");
437
1094
  const sessionKey = getRequestSessionKey(context, options);
438
1095
  requestAbort = new AbortController();
439
- const requestSignal = options?.signal ? AbortSignal.any([options.signal, requestAbort.signal]) : requestAbort.signal;
440
- response = await sendAntigravityRequest({
441
- model,
442
- context,
443
- streamOptions: options,
444
- accessToken,
1096
+ detachCaller = forwardAbort(options?.signal, requestAbort);
1097
+ response = await open(
445
1098
  sessionKey,
446
- signal: requestSignal
447
- });
1099
+ requestAbort.signal,
1100
+ selectedCredentials
1101
+ );
448
1102
  if (!response.ok) {
449
1103
  throw new Error(
450
1104
  `Antigravity request failed: HTTP ${response.status} ${await response.text()}`
@@ -641,16 +1295,221 @@ function streamCortexKitAntigravity(model, context, options) {
641
1295
  await response?.body?.cancel().catch(() => {
642
1296
  });
643
1297
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
644
- output.errorMessage = error instanceof Error ? error.message : String(error);
1298
+ output.errorMessage = redactCredentialText(
1299
+ error instanceof Error ? error.message : String(error),
1300
+ [...selectedCredentials]
1301
+ );
645
1302
  stream.push({ type: "error", reason: output.stopReason, error: output });
646
1303
  stream.end();
1304
+ } finally {
1305
+ detachCaller();
647
1306
  }
648
1307
  })();
649
1308
  return stream;
650
1309
  }
651
1310
 
1311
+ // src/vault-source.ts
1312
+ import { existsSync } from "node:fs";
1313
+ import {
1314
+ createAntigravityVaultAccountSource,
1315
+ enrollmentAuthority as enrollmentAuthority2,
1316
+ loadCommonAuthClaustrum
1317
+ } from "@cortexkit/antigravity-auth-core";
1318
+ var PiVaultSendError = class extends Error {
1319
+ constructor(message) {
1320
+ super(message);
1321
+ this.name = "PiVaultSendError";
1322
+ }
1323
+ };
1324
+ function requireCustody(read) {
1325
+ if (!read.ok)
1326
+ throw new PiVaultSendError(
1327
+ `Antigravity vault mode is unclear (${read.reason}). Choose a mode in the Vault section of the Antigravity menu.`
1328
+ );
1329
+ if (read.record.mode !== "custody")
1330
+ throw new PiVaultSendError(
1331
+ "Antigravity is not served from the vault. Switch in the Vault section of the Antigravity menu."
1332
+ );
1333
+ return read.record;
1334
+ }
1335
+ function selectRoute(routes, routeId) {
1336
+ if (routeId !== void 0) {
1337
+ const selected = routes.find((route) => route.routeId === routeId);
1338
+ if (!selected)
1339
+ throw new PiVaultSendError(
1340
+ "The chosen vault account is no longer available (declined, removed or changed). Choose another in the Vault section of the Antigravity menu."
1341
+ );
1342
+ return selected;
1343
+ }
1344
+ const [only, ...rest] = routes;
1345
+ if (!only)
1346
+ throw new PiVaultSendError(
1347
+ "The vault serves Pi no Antigravity accounts yet."
1348
+ );
1349
+ if (rest.length > 0)
1350
+ throw new PiVaultSendError(
1351
+ "The vault serves Pi several Antigravity accounts. Choose one in the Vault section of the Antigravity menu."
1352
+ );
1353
+ return only;
1354
+ }
1355
+ async function openCustody(options) {
1356
+ const record = requireCustody(await options.readMode());
1357
+ const source = await options.source();
1358
+ return { record, source };
1359
+ }
1360
+ function createPiVaultSend(options) {
1361
+ return async (dispatch, signal) => {
1362
+ const { record, source } = await openCustody(options);
1363
+ signal.throwIfAborted();
1364
+ let routes = source.routes();
1365
+ if (routes.length === 0) {
1366
+ await source.refresh();
1367
+ signal.throwIfAborted();
1368
+ routes = source.routes();
1369
+ }
1370
+ const ref = selectRoute(routes, record.routeId);
1371
+ return source.send(ref, dispatch, { site: "model", signal });
1372
+ };
1373
+ }
1374
+ function createPiVaultAccountsPort(options) {
1375
+ let lastRows = /* @__PURE__ */ new Map();
1376
+ const refFor = (routeId) => {
1377
+ const ref = lastRows.get(routeId);
1378
+ if (!ref)
1379
+ throw new PiVaultSendError(
1380
+ "That vault account is no longer listed. Open the Vault section again."
1381
+ );
1382
+ return ref;
1383
+ };
1384
+ return {
1385
+ list: async () => {
1386
+ const { source } = await openCustody(options);
1387
+ const roster = await source.refresh();
1388
+ const rows = /* @__PURE__ */ new Map();
1389
+ const choices = [];
1390
+ for (const row of roster?.rows ?? []) {
1391
+ if (!row.accountIdentity) continue;
1392
+ const ref = {
1393
+ routeId: row.routeId,
1394
+ credentialId: row.credentialId,
1395
+ accountIdentity: row.accountIdentity,
1396
+ label: row.label,
1397
+ ...row.email !== void 0 && { email: row.email }
1398
+ };
1399
+ rows.set(row.routeId, ref);
1400
+ const declined = (roster?.declined ?? []).some(
1401
+ (entry) => entry.credentialId === row.credentialId && (entry.accountIdentity === void 0 || entry.accountIdentity === row.accountIdentity)
1402
+ );
1403
+ choices.push({
1404
+ routeId: row.routeId,
1405
+ label: row.label,
1406
+ ...row.email !== void 0 && { email: row.email },
1407
+ declined
1408
+ });
1409
+ }
1410
+ lastRows = rows;
1411
+ return choices;
1412
+ },
1413
+ decline: async (routeId) => {
1414
+ const { source } = await openCustody(options);
1415
+ await source.decline(refFor(routeId));
1416
+ },
1417
+ accept: async (routeId) => {
1418
+ const { source } = await openCustody(options);
1419
+ await source.accept(refFor(routeId));
1420
+ }
1421
+ };
1422
+ }
1423
+ async function claustrumConnectionFile() {
1424
+ const { resolveClaustrumConnectionPath } = await import("@cortexkit/claustrum-client");
1425
+ return resolveClaustrumConnectionPath();
1426
+ }
1427
+ function memoize(load) {
1428
+ let pending;
1429
+ return () => {
1430
+ pending ??= load().catch((error) => {
1431
+ pending = void 0;
1432
+ throw error;
1433
+ });
1434
+ return pending;
1435
+ };
1436
+ }
1437
+ function createPiVaultRuntime(readHostSlot, seams = {}, onModeChanged) {
1438
+ const vaultDir = seams.vaultDir ?? getPiAntigravityVaultDir();
1439
+ const modeFile = seams.modeFile ?? getPiAntigravityVaultModeFile();
1440
+ const rosterFile = seams.rosterFile ?? getPiAntigravityVaultRosterFile();
1441
+ const loadClaustrum = seams.loadClaustrum ?? loadCommonAuthClaustrum;
1442
+ const readMode = () => readPiVaultMode(modeFile);
1443
+ const source = memoize(async () => {
1444
+ const claustrum = await loadClaustrum();
1445
+ const { tokenPath } = claustrum.hostEnrollmentPaths({
1446
+ stateDir: vaultDir,
1447
+ host: PI_ANTIGRAVITY_VAULT_HOST
1448
+ });
1449
+ return createAntigravityVaultAccountSource({
1450
+ claustrum,
1451
+ host: PI_ANTIGRAVITY_VAULT_HOST,
1452
+ rosterPath: rosterFile,
1453
+ tokenPath,
1454
+ connect: seams.connect ?? (async () => claustrum.connectClaustrumScopedClient({
1455
+ connectionFile: await claustrumConnectionFile()
1456
+ })),
1457
+ // The vault serves this host only once setup has stored an approved
1458
+ // enrollment token; Pi's own send path also requires custody mode.
1459
+ isCustodyActive: () => existsSync(tokenPath),
1460
+ // Pi reads the HTTP status of each send itself.
1461
+ reporterSource: "direct"
1462
+ });
1463
+ });
1464
+ const setup = memoize(async () => {
1465
+ const claustrum = await loadClaustrum();
1466
+ return new PiVaultSetup({
1467
+ claustrum,
1468
+ stateDir: vaultDir,
1469
+ modeFile,
1470
+ readHostSlot,
1471
+ ...onModeChanged && { onModeChanged },
1472
+ connectEnrollment: seams.connectEnrollment ?? (async () => claustrum.connectClaustrumEnrollmentClient({
1473
+ connectionFile: await claustrumConnectionFile()
1474
+ }))
1475
+ });
1476
+ });
1477
+ return {
1478
+ readMode,
1479
+ setup,
1480
+ requestPath: {
1481
+ readMode: async () => {
1482
+ const settings = await setup();
1483
+ const status = await settings.sourceStatus();
1484
+ const authority = await enrollmentAuthority2(
1485
+ settings.paths,
1486
+ PI_ANTIGRAVITY_ENROLLMENT_NAME
1487
+ );
1488
+ if (authority === "local" && status.mode === "custody")
1489
+ throw new PiVaultSendError(
1490
+ "Vault enrollment is not active. No request was sent."
1491
+ );
1492
+ if (status.mode === "unreadable")
1493
+ throw new PiVaultSendError(
1494
+ "Vault enrollment or source settings cannot be read."
1495
+ );
1496
+ return {
1497
+ ok: true,
1498
+ record: {
1499
+ version: 1,
1500
+ mode: status.mode,
1501
+ ...status.selectedRouteId ? { routeId: status.selectedRouteId } : {}
1502
+ }
1503
+ };
1504
+ },
1505
+ source
1506
+ }
1507
+ };
1508
+ }
1509
+
652
1510
  // src/index.ts
653
- var ANTIGRAVITY_PROVIDER_ID = "google-antigravity";
1511
+ var CUSTODY_PLACEHOLDER_REFRESH_PREFIX = "claustrum-tombstone:v1:";
1512
+ var CUSTODY_PROVIDER_KEY_MARKER = "antigravity-vault-custody";
654
1513
  function textImageInput() {
655
1514
  return ["text", "image"];
656
1515
  }
@@ -682,6 +1541,10 @@ async function loginAntigravity(callbacks) {
682
1541
  };
683
1542
  }
684
1543
  async function refreshAntigravityCredentials(credentials) {
1544
+ if (credentials.refresh.startsWith(CUSTODY_PLACEHOLDER_REFRESH_PREFIX))
1545
+ throw new Error(
1546
+ "Pi holds the Antigravity vault placeholder, not a login. Run /login, or serve Antigravity from the vault."
1547
+ );
685
1548
  const refreshToken = credentials.refresh.split("|")[0] ?? credentials.refresh;
686
1549
  const refreshed = await refreshAntigravityToken(refreshToken);
687
1550
  const projectSegments = credentials.refresh.includes("|") ? credentials.refresh.slice(credentials.refresh.indexOf("|")) : "";
@@ -691,8 +1554,8 @@ async function refreshAntigravityCredentials(credentials) {
691
1554
  expires: refreshed.expires
692
1555
  };
693
1556
  }
694
- function cortexKitPiAntigravityAuth(pi) {
695
- const models = Object.values(getPublicModelDefinitions()).filter((model) => !model.modalities.output.includes("image")).map((model) => ({
1557
+ function publicModels() {
1558
+ return Object.values(getPublicModelDefinitions()).filter((model) => !model.modalities.output.includes("image")).map((model) => ({
696
1559
  id: model.id,
697
1560
  name: model.name,
698
1561
  reasoning: model.reasoning,
@@ -701,24 +1564,187 @@ function cortexKitPiAntigravityAuth(pi) {
701
1564
  contextWindow: model.limit.context,
702
1565
  maxTokens: model.limit.output
703
1566
  }));
704
- pi.registerProvider(ANTIGRAVITY_PROVIDER_ID, {
1567
+ }
1568
+ function localProviderConfig(models, assertLocalAdmission) {
1569
+ return {
705
1570
  name: "Google Antigravity (CortexKit OAuth)",
706
1571
  baseUrl: "https://cloudcode-pa.googleapis.com",
707
1572
  api: "google-generative-ai",
708
1573
  models,
709
1574
  oauth: {
710
1575
  name: "Google Antigravity (CortexKit)",
711
- login: loginAntigravity,
712
- refreshToken: refreshAntigravityCredentials,
1576
+ login: async (callbacks) => {
1577
+ await assertLocalAdmission();
1578
+ return loginAntigravity(callbacks);
1579
+ },
1580
+ refreshToken: async (credentials) => {
1581
+ await assertLocalAdmission();
1582
+ return refreshAntigravityCredentials(credentials);
1583
+ },
713
1584
  getApiKey: (credentials) => {
714
1585
  rememberPackedRefresh(credentials.access, credentials.refresh);
715
1586
  return credentials.access;
716
1587
  }
717
1588
  },
718
- streamSimple: streamCortexKitAntigravity
1589
+ streamSimple: (model, context, options) => streamCortexKitAntigravity(model, context, options, assertLocalAdmission)
1590
+ };
1591
+ }
1592
+ function custodyProviderConfig(models, send) {
1593
+ return {
1594
+ name: "Google Antigravity (CortexKit vault)",
1595
+ baseUrl: "https://cloudcode-pa.googleapis.com",
1596
+ api: "google-generative-ai",
1597
+ models,
1598
+ apiKey: CUSTODY_PROVIDER_KEY_MARKER,
1599
+ streamSimple: createCortexKitAntigravityVaultStream(
1600
+ send,
1601
+ CUSTODY_PROVIDER_KEY_MARKER
1602
+ )
1603
+ };
1604
+ }
1605
+ function refusingProviderConfig(models, reason) {
1606
+ return {
1607
+ name: "Google Antigravity (CortexKit, unavailable)",
1608
+ baseUrl: "https://cloudcode-pa.googleapis.com",
1609
+ api: "google-generative-ai",
1610
+ models,
1611
+ apiKey: CUSTODY_PROVIDER_KEY_MARKER,
1612
+ streamSimple: createCortexKitAntigravityVaultStream(async () => {
1613
+ throw new PiVaultSetupError(
1614
+ `${reason} Resolve it from /antigravity; Antigravity requests are refused until then. No request was sent.`
1615
+ );
1616
+ }, CUSTODY_PROVIDER_KEY_MARKER)
1617
+ };
1618
+ }
1619
+ async function registerCortexKitPiAntigravity(pi, seams = {}) {
1620
+ const models = publicModels();
1621
+ const readHostSlot = seams.readHostSlot ?? (() => readStoredCredential(ANTIGRAVITY_PROVIDER_ID2));
1622
+ let refreshRegistry;
1623
+ const vault = createPiVaultRuntime(readHostSlot, seams, async () => {
1624
+ try {
1625
+ await synchronizeSlot();
1626
+ } finally {
1627
+ refreshRegistry?.();
1628
+ }
719
1629
  });
1630
+ const send = createPiVaultSend(vault.requestPath);
1631
+ const register = (mode) => {
1632
+ const local = mode.ok && mode.record.mode === "local";
1633
+ pi.registerProvider(
1634
+ ANTIGRAVITY_PROVIDER_ID2,
1635
+ local ? localProviderConfig(models, assertLocalAdmission) : custodyProviderConfig(models, send)
1636
+ );
1637
+ };
1638
+ const currentMode = async () => {
1639
+ const setup = await vault.setup();
1640
+ const status = await setup.sourceStatus();
1641
+ const authority = await enrollmentAuthority3(
1642
+ setup.paths,
1643
+ PI_ANTIGRAVITY_ENROLLMENT_NAME
1644
+ );
1645
+ if (authority === "unreadable" || status.mode === "unreadable") {
1646
+ throw new PiVaultSetupError(
1647
+ "Vault enrollment or source settings cannot be read. Local credentials remain unavailable."
1648
+ );
1649
+ }
1650
+ if (authority === "vault")
1651
+ return {
1652
+ version: 1,
1653
+ mode: "custody",
1654
+ ...status.selectedRouteId ? { routeId: status.selectedRouteId } : {}
1655
+ };
1656
+ if (status.mode !== "local")
1657
+ throw new PiVaultSetupError(
1658
+ "Vault enrollment must be approved before activating vault credentials."
1659
+ );
1660
+ return { version: 1, mode: "local" };
1661
+ };
1662
+ const assertLocalAdmission = async () => {
1663
+ if ((await currentMode()).mode !== "local")
1664
+ throw new PiVaultSetupError(
1665
+ "Vault enrollment excludes local login, refresh and request execution."
1666
+ );
1667
+ };
1668
+ const synchronizeSlotOnce = async () => {
1669
+ try {
1670
+ await swapSlotForMode();
1671
+ } catch (error) {
1672
+ pi.registerProvider(
1673
+ ANTIGRAVITY_PROVIDER_ID2,
1674
+ refusingProviderConfig(
1675
+ models,
1676
+ error instanceof Error ? error.message : String(error)
1677
+ )
1678
+ );
1679
+ throw error;
1680
+ }
1681
+ };
1682
+ let synchronizing = Promise.resolve();
1683
+ const synchronizeSlot = () => {
1684
+ const run = synchronizing.then(synchronizeSlotOnce);
1685
+ synchronizing = run.catch(() => {
1686
+ });
1687
+ return run;
1688
+ };
1689
+ const swapSlotForMode = async () => {
1690
+ const mode = await currentMode();
1691
+ const { openPiSlot } = await (seams.loadPiSlot ?? loadCommonAuthPiSlot)();
1692
+ const slot = openPiSlot({
1693
+ authPath: seams.nativeAuthPath ?? join2(getAgentDir(), "auth.json"),
1694
+ stashPath: seams.nativeStashPath ?? join2(
1695
+ seams.vaultDir ?? getPiAntigravityVaultDir(),
1696
+ "google-login-stash.json"
1697
+ ),
1698
+ provider: ANTIGRAVITY_PROVIDER_ID2,
1699
+ placeholderKey: CUSTODY_PROVIDER_KEY_MARKER
1700
+ });
1701
+ if (mode.mode === "custody") await slot.enterVault();
1702
+ else if (await slot.exitVault() === "conflict")
1703
+ throw new PiVaultSetupError(
1704
+ "The native Google login changed. It was not overwritten; restore the saved login after resolving the conflict."
1705
+ );
1706
+ const after = await currentMode();
1707
+ if (after.mode !== mode.mode)
1708
+ throw new PiVaultSetupError(
1709
+ "Vault enrollment changed while the credential slot was being synchronized. The account source was not activated."
1710
+ );
1711
+ register({ ok: true, record: after });
1712
+ };
1713
+ await synchronizeSlot().catch(() => {
1714
+ });
1715
+ pi.on?.("session_start", async (_event, context) => {
1716
+ refreshRegistry = () => context.modelRegistry.refresh();
1717
+ try {
1718
+ await synchronizeSlot();
1719
+ } finally {
1720
+ refreshRegistry();
1721
+ }
1722
+ });
1723
+ const accounts = createPiVaultAccountsPort(vault.requestPath);
1724
+ const extension = {
1725
+ accountSections: createPiAntigravityAccountSections({
1726
+ setup: vault.setup,
1727
+ accounts: () => accounts
1728
+ }),
1729
+ vaultSection: createPiVaultSection({
1730
+ setup: vault.setup,
1731
+ accounts: () => accounts
1732
+ })
1733
+ };
1734
+ registerPiAntigravityCommand(pi, {
1735
+ sections: extension.accountSections,
1736
+ extras: [extension.vaultSection],
1737
+ ...seams.loadCommands && { loadCommands: seams.loadCommands }
1738
+ });
1739
+ return extension;
1740
+ }
1741
+ async function cortexKitPiAntigravityAuth(pi) {
1742
+ await registerCortexKitPiAntigravity(pi);
720
1743
  }
721
1744
  export {
722
- cortexKitPiAntigravityAuth as default
1745
+ CUSTODY_PLACEHOLDER_REFRESH_PREFIX,
1746
+ CUSTODY_PROVIDER_KEY_MARKER,
1747
+ cortexKitPiAntigravityAuth as default,
1748
+ registerCortexKitPiAntigravity
723
1749
  };
724
1750
  //# sourceMappingURL=index.js.map