@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/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 = convertTools(context.tools);
166
- if (tools) request.tools = tools;
167
- if (context.systemPrompt?.trim()) {
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(context.systemPrompt) }]
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[0]?.timestamp;
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
- async function sendAntigravityRequest(options) {
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
- 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
- const request = buildGeminiRequest(options.context, {
373
- provider: options.model.provider,
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: projectContext.effectiveProjectId,
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 ${options.accessToken}`,
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: options.signal ?? options.streamOptions?.signal ?? null }
1014
+ { signal }
423
1015
  );
424
1016
  }
425
- function streamCortexKitAntigravity(model, context, options) {
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
- 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,
1108
+ detachCaller = forwardAbort(options?.signal, requestAbort);
1109
+ response = await open(
445
1110
  sessionKey,
446
- signal: requestSignal
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 = error instanceof Error ? error.message : String(error);
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 ANTIGRAVITY_PROVIDER_ID = "google-antigravity";
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 cortexKitPiAntigravityAuth(pi) {
695
- const models = Object.values(getPublicModelDefinitions()).filter((model) => !model.modalities.output.includes("image")).map((model) => ({
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
- pi.registerProvider(ANTIGRAVITY_PROVIDER_ID, {
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: loginAntigravity,
712
- refreshToken: refreshAntigravityCredentials,
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
- cortexKitPiAntigravityAuth as default
1757
+ CUSTODY_PLACEHOLDER_REFRESH_PREFIX,
1758
+ CUSTODY_PROVIDER_KEY_MARKER,
1759
+ cortexKitPiAntigravityAuth as default,
1760
+ registerCortexKitPiAntigravity
723
1761
  };
724
1762
  //# sourceMappingURL=index.js.map