antigravity-claude-proxy 2.7.2 → 2.7.4

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/public/index.html CHANGED
@@ -9,8 +9,6 @@
9
9
 
10
10
  <!-- Libraries -->
11
11
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
12
- <!-- Alpine.js Plugins -->
13
- <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
14
12
  <!-- Alpine.js must be deferred so stores register their listeners first -->
15
13
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
16
14
 
@@ -212,127 +212,6 @@ window.Components.accountManager = () => ({
212
212
  newModelThreshold: 10
213
213
  },
214
214
 
215
- // Fingerprint Modal
216
- fingerprintModal: {
217
- email: '',
218
- current: null,
219
- history: [],
220
- loading: false,
221
- regenerating: false,
222
- restoring: false
223
- },
224
-
225
- async openFingerprintModal(account) {
226
- const store = Alpine.store('global');
227
- this.fingerprintModal = {
228
- email: account.email,
229
- current: null,
230
- history: [],
231
- loading: true,
232
- regenerating: false,
233
- restoring: false
234
- };
235
-
236
- document.getElementById('fingerprint_modal').showModal();
237
-
238
- try {
239
- const { response, newPassword } = await window.utils.request(
240
- `/api/accounts/${encodeURIComponent(account.email)}/fingerprint`,
241
- {},
242
- store.webuiPassword
243
- );
244
- if (newPassword) store.webuiPassword = newPassword;
245
-
246
- const data = await response.json();
247
- if (data.status === 'ok') {
248
- this.fingerprintModal.current = data.fingerprint;
249
- this.fingerprintModal.history = data.history;
250
- } else {
251
- store.showToast(data.error || 'Failed to fetch fingerprint', 'error');
252
- }
253
- } catch (e) {
254
- store.showToast('Error fetching fingerprint: ' + e.message, 'error');
255
- } finally {
256
- this.fingerprintModal.loading = false;
257
- }
258
- },
259
-
260
- async regenerateFingerprint() {
261
- const store = Alpine.store('global');
262
- const email = this.fingerprintModal.email;
263
- this.fingerprintModal.regenerating = true;
264
-
265
- try {
266
- const { response, newPassword } = await window.utils.request(
267
- `/api/accounts/${encodeURIComponent(email)}/fingerprint/regenerate`,
268
- { method: 'POST' },
269
- store.webuiPassword
270
- );
271
- if (newPassword) store.webuiPassword = newPassword;
272
-
273
- const data = await response.json();
274
- if (data.status === 'ok') {
275
- store.showToast('Fingerprint regenerated', 'success');
276
- // Refresh modal data
277
- this.fingerprintModal.current = data.fingerprint;
278
- // Fetch history again to show the old one moved to history
279
- await this.refreshFingerprintData(email);
280
- } else {
281
- throw new Error(data.error || 'Failed to regenerate');
282
- }
283
- } catch (e) {
284
- store.showToast('Regeneration failed: ' + e.message, 'error');
285
- } finally {
286
- this.fingerprintModal.regenerating = false;
287
- }
288
- },
289
-
290
- async restoreFingerprint(index) {
291
- const store = Alpine.store('global');
292
- const email = this.fingerprintModal.email;
293
- this.fingerprintModal.restoring = true;
294
-
295
- try {
296
- const { response, newPassword } = await window.utils.request(
297
- `/api/accounts/${encodeURIComponent(email)}/fingerprint/restore`,
298
- {
299
- method: 'POST',
300
- headers: { 'Content-Type': 'application/json' },
301
- body: JSON.stringify({ index })
302
- },
303
- store.webuiPassword
304
- );
305
- if (newPassword) store.webuiPassword = newPassword;
306
-
307
- const data = await response.json();
308
- if (data.status === 'ok') {
309
- store.showToast('Fingerprint restored', 'success');
310
- this.fingerprintModal.current = data.fingerprint;
311
- await this.refreshFingerprintData(email);
312
- } else {
313
- throw new Error(data.error || 'Failed to restore');
314
- }
315
- } catch (e) {
316
- store.showToast('Restore failed: ' + e.message, 'error');
317
- } finally {
318
- this.fingerprintModal.restoring = false;
319
- }
320
- },
321
-
322
- async refreshFingerprintData(email) {
323
- const store = Alpine.store('global');
324
- const { response, newPassword } = await window.utils.request(
325
- `/api/accounts/${encodeURIComponent(email)}/fingerprint`,
326
- {},
327
- store.webuiPassword
328
- );
329
- if (newPassword) store.webuiPassword = newPassword;
330
- const data = await response.json();
331
- if (data.status === 'ok') {
332
- this.fingerprintModal.history = data.history;
333
- }
334
- },
335
-
336
215
  openThresholdModal(account) {
337
216
  this.thresholdDialog = {
338
217
  email: account.email,
@@ -88,7 +88,6 @@
88
88
  <th class="pl-6 py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-16" x-text="$store.global.t('enabled')">Enabled</th>
89
89
  <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider flex-1 min-w-[200px]" x-text="$store.global.t('accountEmail')">Account (Email)</th>
90
90
  <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-20" x-text="$store.global.t('source')">Source</th>
91
- <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-24">Fingerprint</th>
92
91
  <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-16" x-text="$store.global.t('tier')">Tier</th>
93
92
  <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-32" x-text="$store.global.t('quota')">Quota</th>
94
93
  <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-24" x-text="$store.global.t('health')">Health</th>
@@ -152,15 +151,6 @@
152
151
  x-text="acc.source || 'oauth'">
153
152
  </span>
154
153
  </td>
155
- <td class="py-4">
156
- <button class="p-2 rounded-lg hover:bg-space-800 text-gray-500 hover:text-neon-cyan transition-colors"
157
- @click="openFingerprintModal(acc)"
158
- :title="'View Fingerprint for ' + formatEmail(acc.email)">
159
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
160
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.2-2.873.571-4.241m2.823 10.36c.682-1.365 1.178-2.791 1.406-4.117m8.406 5.828a5.74 5.74 0 00-1.552-1.433" />
161
- </svg>
162
- </button>
163
- </td>
164
154
  <td class="py-4">
165
155
  <span :class="{
166
156
  'status-pill-ultra': acc.subscription?.tier === 'ultra',
@@ -647,143 +637,4 @@
647
637
  <button>close</button>
648
638
  </form>
649
639
  </dialog>
650
-
651
- <!-- Fingerprint Details Modal -->
652
- <dialog id="fingerprint_modal" class="modal backdrop-blur-sm">
653
- <div class="modal-box max-w-2xl w-full bg-space-900 border border-space-border text-gray-300 shadow-2xl p-6 relative">
654
- <!-- Close Button (Top Right) -->
655
- <form method="dialog" class="absolute top-4 right-4">
656
- <button class="btn btn-sm btn-circle btn-ghost text-gray-500 hover:text-white">
657
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
658
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
659
- </svg>
660
- </button>
661
- </form>
662
-
663
- <h3 class="font-bold text-xl text-white mb-2 flex items-center gap-2">
664
- <svg class="w-6 h-6 text-neon-cyan" fill="none" stroke="currentColor" viewBox="0 0 24 24">
665
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.2-2.873.571-4.241m2.823 10.36c.682-1.365 1.178-2.791 1.406-4.117m8.406 5.828a5.74 5.74 0 00-1.552-1.433" />
666
- </svg>
667
- <span>Device Fingerprint</span>
668
- </h3>
669
- <p class="text-sm text-gray-500 font-mono mb-6" x-text="Redact.email(fingerprintModal.email)"></p>
670
-
671
- <div x-show="fingerprintModal.loading" class="flex justify-center py-8">
672
- <span class="loading loading-spinner text-neon-cyan"></span>
673
- </div>
674
-
675
- <div x-show="!fingerprintModal.loading && fingerprintModal.current">
676
- <!-- Current Fingerprint -->
677
- <div class="bg-space-800/50 border border-space-border/30 rounded-lg p-4 mb-6 relative overflow-hidden">
678
- <div class="absolute top-0 right-0 p-2 opacity-10">
679
- <svg class="w-24 h-24 text-neon-cyan" fill="none" stroke="currentColor" viewBox="0 0 24 24">
680
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.2-2.873.571-4.241m2.823 10.36c.682-1.365 1.178-2.791 1.406-4.117m8.406 5.828a5.74 5.74 0 00-1.552-1.433" />
681
- </svg>
682
- </div>
683
-
684
- <div class="grid grid-cols-1 gap-3 relative z-10">
685
- <div>
686
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">Device ID</span>
687
- <div class="font-mono text-sm text-neon-cyan break-all" x-text="fingerprintModal.current?.deviceId"></div>
688
- </div>
689
- <div>
690
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">User Agent</span>
691
- <div class="font-mono text-xs text-gray-300 break-words bg-space-900/50 p-2 rounded border border-space-border/20 w-fit max-w-full"
692
- x-text="fingerprintModal.current?.userAgent"></div>
693
- </div>
694
- <div class="grid grid-cols-2 gap-4">
695
- <div>
696
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">Platform</span>
697
- <div class="font-mono text-xs text-gray-300"
698
- x-text="fingerprintModal.current?.clientMetadata?.platform || 'UNK'"></div>
699
- </div>
700
- <div>
701
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">Created</span>
702
- <div class="font-mono text-xs text-gray-400"
703
- x-text="new Date(fingerprintModal.current?.createdAt).toLocaleString()"></div>
704
- </div>
705
- </div>
706
- </div>
707
-
708
- <!-- Advanced Details (Collapsible) -->
709
- <div class="mt-4 pt-4 border-t border-space-border/20" x-data="{ expanded: false }">
710
- <button @click="expanded = !expanded"
711
- class="flex items-center gap-2 text-[10px] uppercase text-gray-500 font-bold tracking-wider hover:text-neon-cyan transition-colors w-full">
712
- <svg class="w-3 h-3 transition-transform" :class="{ 'rotate-90': expanded }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
713
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
714
- </svg>
715
- Advanced Details
716
- </button>
717
-
718
- <div x-show="expanded" x-collapse class="mt-3 grid grid-cols-1 gap-3 relative z-10">
719
- <div>
720
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">API Client</span>
721
- <div class="font-mono text-xs text-gray-300 break-words" x-text="fingerprintModal.current?.apiClient"></div>
722
- </div>
723
- <div>
724
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">Quota User</span>
725
- <div class="font-mono text-xs text-gray-300 break-all" x-text="fingerprintModal.current?.quotaUser"></div>
726
- </div>
727
- <div>
728
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">Session Token</span>
729
- <div class="font-mono text-xs text-gray-300 break-all" x-text="fingerprintModal.current?.sessionToken"></div>
730
- </div>
731
- <div>
732
- <span class="text-[10px] uppercase text-gray-500 font-bold tracking-wider">Metadata</span>
733
- <div class="font-mono text-[10px] text-gray-400 bg-space-900/50 p-2 rounded border border-space-border/20 mt-1">
734
- <div class="grid grid-cols-2 gap-x-4 gap-y-1">
735
- <div><span class="text-gray-600">OS Version:</span> <span class="text-gray-300" x-text="fingerprintModal.current?.clientMetadata?.osVersion"></span></div>
736
- <div><span class="text-gray-600">Arch:</span> <span class="text-gray-300" x-text="fingerprintModal.current?.clientMetadata?.arch"></span></div>
737
- <div><span class="text-gray-600">IDE:</span> <span class="text-gray-300" x-text="fingerprintModal.current?.clientMetadata?.ideType"></span></div>
738
- <div><span class="text-gray-600">Plugin:</span> <span class="text-gray-300" x-text="fingerprintModal.current?.clientMetadata?.pluginType"></span></div>
739
- </div>
740
- </div>
741
- </div>
742
- </div>
743
- </div>
744
- </div>
745
-
746
- <!-- Actions -->
747
- <div class="flex justify-end mb-6">
748
- <button class="btn btn-sm btn-outline border-neon-cyan text-neon-cyan hover:bg-neon-cyan hover:text-black gap-2"
749
- @click="regenerateFingerprint()"
750
- :disabled="fingerprintModal.regenerating">
751
- <svg class="w-4 h-4" :class="{ 'animate-spin': fingerprintModal.regenerating }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
752
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
753
- </svg>
754
- <span x-text="fingerprintModal.regenerating ? 'Regenerating...' : 'Regenerate Fingerprint'"></span>
755
- </button>
756
- </div>
757
-
758
- <!-- History -->
759
- <div x-show="fingerprintModal.history.length > 0">
760
- <h4 class="text-sm font-bold text-gray-400 mb-3 uppercase tracking-wider">History</h4>
761
- <div class="space-y-2 max-h-40 overflow-y-auto custom-scrollbar">
762
- <template x-for="(entry, index) in fingerprintModal.history" :key="entry.timestamp">
763
- <div class="flex items-center justify-between p-3 bg-space-800/30 border border-space-border/20 rounded hover:bg-space-800/50 transition-colors">
764
- <div class="flex-1 min-w-0 mr-4">
765
- <div class="flex items-center gap-2 mb-1">
766
- <span class="text-xs font-mono text-gray-400 truncate" x-text="entry.fingerprint.deviceId"></span>
767
- <span class="text-[10px] px-1.5 py-0.5 rounded bg-space-700 text-gray-500" x-text="entry.reason"></span>
768
- </div>
769
- <div class="text-[10px] text-gray-600 font-mono" x-text="new Date(entry.timestamp).toLocaleString()"></div>
770
- </div>
771
- <button class="btn btn-xs btn-ghost text-gray-500 hover:text-white"
772
- @click="restoreFingerprint(index)"
773
- :disabled="fingerprintModal.restoring"
774
- title="Restore this fingerprint">
775
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
776
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
777
- </svg>
778
- </button>
779
- </div>
780
- </template>
781
- </div>
782
- </div>
783
- </div>
784
- </div>
785
- <form method="dialog" class="modal-backdrop">
786
- <button>close</button>
787
- </form>
788
- </dialog>
789
640
  </div>
@@ -235,7 +235,7 @@ export async function discoverProject(token, projectId = undefined) {
235
235
  'Content-Type': 'application/json',
236
236
  ...LOAD_CODE_ASSIST_HEADERS
237
237
  },
238
- body: JSON.stringify({ metadata })
238
+ body: JSON.stringify({ metadata, mode: 1 })
239
239
  });
240
240
 
241
241
  if (!response.ok) {
@@ -35,7 +35,6 @@ import {
35
35
  } from './credentials.js';
36
36
  import { createStrategy, getStrategyLabel, DEFAULT_STRATEGY } from './strategies/index.js';
37
37
  import { logger } from '../utils/logger.js';
38
- import { generateFingerprint, MAX_FINGERPRINT_HISTORY } from '../utils/fingerprint.js';
39
38
 
40
39
  export class AccountManager {
41
40
  #accounts = [];
@@ -459,8 +458,7 @@ export class AccountManager {
459
458
  lastUsed: a.lastUsed,
460
459
  // Include quota threshold settings
461
460
  quotaThreshold: a.quotaThreshold,
462
- modelQuotaThresholds: a.modelQuotaThresholds || {},
463
- hasFingerprint: !!a.fingerprint
461
+ modelQuotaThresholds: a.modelQuotaThresholds || {}
464
462
  }))
465
463
  };
466
464
  }
@@ -525,88 +523,6 @@ export class AccountManager {
525
523
  getAllAccounts() {
526
524
  return this.#accounts;
527
525
  }
528
-
529
- /**
530
- * Regenerate fingerprint for an account
531
- * @param {string} email - Email of the account
532
- * @returns {Object|null} New fingerprint or null if account not found
533
- */
534
- regenerateFingerprint(email) {
535
- const account = this.#accounts.find(a => a.email === email);
536
- if (!account) return null;
537
-
538
- if (account.fingerprint) {
539
- const historyEntry = {
540
- fingerprint: account.fingerprint,
541
- timestamp: Date.now(),
542
- reason: 'regenerated'
543
- };
544
-
545
- if (!account.fingerprintHistory) {
546
- account.fingerprintHistory = [];
547
- }
548
-
549
- account.fingerprintHistory.unshift(historyEntry);
550
- if (account.fingerprintHistory.length > MAX_FINGERPRINT_HISTORY) {
551
- account.fingerprintHistory = account.fingerprintHistory.slice(0, MAX_FINGERPRINT_HISTORY);
552
- }
553
- }
554
-
555
- account.fingerprint = generateFingerprint();
556
- this.saveToDisk();
557
- return account.fingerprint;
558
- }
559
-
560
- /**
561
- * Restore fingerprint from history
562
- * @param {string} email - Email of the account
563
- * @param {number} historyIndex - Index in history array (0 is most recent)
564
- * @returns {Object|null} Restored fingerprint or null
565
- */
566
- restoreFingerprint(email, historyIndex) {
567
- const account = this.#accounts.find(a => a.email === email);
568
- if (!account || !account.fingerprintHistory || !account.fingerprintHistory[historyIndex]) {
569
- return null;
570
- }
571
-
572
- const restoredEntry = account.fingerprintHistory[historyIndex];
573
-
574
- // Save current to history before restoring
575
- if (account.fingerprint) {
576
- const historyEntry = {
577
- fingerprint: account.fingerprint,
578
- timestamp: Date.now(),
579
- reason: 'restored'
580
- };
581
- // account.fingerprintHistory is guaranteed to exist if we are here
582
- account.fingerprintHistory.unshift(historyEntry);
583
- }
584
-
585
- // Remove the restored entry from history (shifted by 1 if we just unshifted)
586
- const removeIndex = account.fingerprint ? historyIndex + 1 : historyIndex;
587
- account.fingerprintHistory.splice(removeIndex, 1);
588
-
589
- // Restore
590
- account.fingerprint = { ...restoredEntry.fingerprint, createdAt: Date.now() };
591
-
592
- // Trim history again (since we added one)
593
- if (account.fingerprintHistory.length > MAX_FINGERPRINT_HISTORY) {
594
- account.fingerprintHistory = account.fingerprintHistory.slice(0, MAX_FINGERPRINT_HISTORY);
595
- }
596
-
597
- this.saveToDisk();
598
- return account.fingerprint;
599
- }
600
-
601
- /**
602
- * Get fingerprint history
603
- * @param {string} email - Email of the account
604
- * @returns {Array} Array of fingerprint history entries
605
- */
606
- getFingerprintHistory(email) {
607
- const account = this.#accounts.find(a => a.email === email);
608
- return account ? (account.fingerprintHistory || []) : [];
609
- }
610
526
  }
611
527
 
612
528
  // Re-export CooldownReason for use by handlers
@@ -10,7 +10,6 @@ import { dirname } from 'path';
10
10
  import { ACCOUNT_CONFIG_PATH } from '../constants.js';
11
11
  import { getAuthStatus } from '../auth/database.js';
12
12
  import { logger } from '../utils/logger.js';
13
- import { generateFingerprint, updateFingerprintVersion } from '../utils/fingerprint.js';
14
13
 
15
14
  /**
16
15
  * Load accounts from the config file
@@ -40,12 +39,7 @@ export async function loadAccounts(configPath = ACCOUNT_CONFIG_PATH) {
40
39
  quota: acc.quota || { models: {}, lastChecked: null },
41
40
  // Quota threshold settings (per-account and per-model overrides)
42
41
  quotaThreshold: acc.quotaThreshold, // undefined means use global
43
- modelQuotaThresholds: acc.modelQuotaThresholds || {},
44
- // Fingerprint management
45
- fingerprint: acc.fingerprint
46
- ? updateFingerprintVersion(acc.fingerprint)
47
- : generateFingerprint(),
48
- fingerprintHistory: acc.fingerprintHistory || []
42
+ modelQuotaThresholds: acc.modelQuotaThresholds || {}
49
43
  }));
50
44
 
51
45
  const settings = config.settings || {};
@@ -84,8 +78,7 @@ export function loadDefaultAccount(dbPath) {
84
78
  email: authData.email || 'default@antigravity',
85
79
  source: 'database',
86
80
  lastUsed: null,
87
- modelRateLimits: {},
88
- fingerprint: generateFingerprint()
81
+ modelRateLimits: {}
89
82
  };
90
83
 
91
84
  const tokenCache = new Map();
@@ -139,10 +132,7 @@ export async function saveAccounts(configPath, accounts, settings, activeIndex)
139
132
  quota: acc.quota || { models: {}, lastChecked: null },
140
133
  // Persist quota threshold settings
141
134
  quotaThreshold: acc.quotaThreshold, // undefined omitted from JSON
142
- modelQuotaThresholds: Object.keys(acc.modelQuotaThresholds || {}).length > 0 ? acc.modelQuotaThresholds : undefined,
143
- // Persist fingerprint data
144
- fingerprint: acc.fingerprint,
145
- fingerprintHistory: acc.fingerprintHistory && acc.fingerprintHistory.length > 0 ? acc.fingerprintHistory : undefined
135
+ modelQuotaThresholds: Object.keys(acc.modelQuotaThresholds || {}).length > 0 ? acc.modelQuotaThresholds : undefined
146
136
  })),
147
137
  settings: settings,
148
138
  activeIndex: activeIndex
@@ -50,7 +50,7 @@ function isServerRunning() {
50
50
  resolve(false);
51
51
  });
52
52
 
53
- socket.on('error', () => {
53
+ socket.on('error', (err) => {
54
54
  socket.destroy();
55
55
  resolve(false); // Port free
56
56
  });
@@ -143,9 +143,7 @@ function saveAccounts(accounts, settings = {}) {
143
143
  projectId: acc.projectId,
144
144
  addedAt: acc.addedAt || new Date().toISOString(),
145
145
  lastUsed: acc.lastUsed || null,
146
- modelRateLimits: acc.modelRateLimits || {},
147
- fingerprint: acc.fingerprint,
148
- fingerprintHistory: acc.fingerprintHistory
146
+ modelRateLimits: acc.modelRateLimits || {}
149
147
  })),
150
148
  settings: {
151
149
  maxRetries: 5,
@@ -139,7 +139,7 @@ export async function sendMessage(anthropicRequest, accountManager, fallbackEnab
139
139
  // Get token and project for this account
140
140
  const token = await accountManager.getTokenForAccount(account);
141
141
  const project = await accountManager.getProjectForAccount(account, token);
142
- const payload = buildCloudCodeRequest(anthropicRequest, project);
142
+ const payload = buildCloudCodeRequest(anthropicRequest, project, account.email);
143
143
 
144
144
  logger.debug(`[CloudCode] Sending request for model: ${model}`);
145
145
 
@@ -157,7 +157,7 @@ export async function sendMessage(anthropicRequest, accountManager, fallbackEnab
157
157
 
158
158
  const response = await throttledFetch(url, {
159
159
  method: 'POST',
160
- headers: buildHeaders(token, model, isThinking ? 'text/event-stream' : 'application/json', account.fingerprint),
160
+ headers: buildHeaders(token, model, isThinking ? 'text/event-stream' : 'application/json'),
161
161
  body: JSON.stringify(payload)
162
162
  });
163
163
 
@@ -49,12 +49,12 @@ export async function listModels(token) {
49
49
  const modelList = Object.entries(data.models)
50
50
  .filter(([modelId]) => isSupportedModel(modelId))
51
51
  .map(([modelId, modelData]) => ({
52
- id: modelId,
53
- object: 'model',
54
- created: Math.floor(Date.now() / 1000),
55
- owned_by: 'anthropic',
56
- description: modelData.displayName || modelId
57
- }));
52
+ id: modelId,
53
+ object: 'model',
54
+ created: Math.floor(Date.now() / 1000),
55
+ owned_by: 'anthropic',
56
+ description: modelData.displayName || modelId
57
+ }));
58
58
 
59
59
  // Warm the model validation cache
60
60
  modelCache.validModels = new Set(modelList.map(m => m.id));
@@ -183,10 +183,8 @@ export async function getSubscriptionTier(token) {
183
183
  method: 'POST',
184
184
  headers,
185
185
  body: JSON.stringify({
186
- metadata: {
187
- ...CLIENT_METADATA,
188
- duetProject: 'rising-fact-p41fc'
189
- }
186
+ metadata: CLIENT_METADATA,
187
+ mode: 1
190
188
  })
191
189
  });
192
190
 
@@ -13,21 +13,21 @@ import {
13
13
  } from '../constants.js';
14
14
  import { convertAnthropicToGoogle } from '../format/index.js';
15
15
  import { deriveSessionId } from './session-manager.js';
16
- import { buildFingerprintHeaders } from '../utils/fingerprint.js';
17
16
 
18
17
  /**
19
18
  * Build the wrapped request body for Cloud Code API
20
19
  *
21
20
  * @param {Object} anthropicRequest - The Anthropic-format request
22
21
  * @param {string} projectId - The project ID to use
22
+ * @param {string} accountEmail - The account email for session ID derivation
23
23
  * @returns {Object} The Cloud Code API request payload
24
24
  */
25
- export function buildCloudCodeRequest(anthropicRequest, projectId) {
25
+ export function buildCloudCodeRequest(anthropicRequest, projectId, accountEmail) {
26
26
  const model = anthropicRequest.model;
27
27
  const googleRequest = convertAnthropicToGoogle(anthropicRequest);
28
28
 
29
29
  // Use stable session ID derived from first user message for cache continuity
30
- googleRequest.sessionId = deriveSessionId(anthropicRequest);
30
+ googleRequest.sessionId = deriveSessionId(anthropicRequest, accountEmail);
31
31
 
32
32
  // Build system instruction parts array with [ignore] tags to prevent model from
33
33
  // identifying as "Antigravity" (fixes GitHub issue #76)
@@ -70,17 +70,13 @@ export function buildCloudCodeRequest(anthropicRequest, projectId) {
70
70
  * @param {string} token - OAuth access token
71
71
  * @param {string} model - Model name
72
72
  * @param {string} accept - Accept header value (default: 'application/json')
73
- * @param {Object} [fingerprint] - Optional device fingerprint for header randomization
74
73
  * @returns {Object} Headers object
75
74
  */
76
- export function buildHeaders(token, model, accept = 'application/json', fingerprint = null) {
77
- const fingerprintHeaders = fingerprint ? buildFingerprintHeaders(fingerprint) : {};
78
-
75
+ export function buildHeaders(token, model, accept = 'application/json') {
79
76
  const headers = {
80
77
  'Authorization': `Bearer ${token}`,
81
78
  'Content-Type': 'application/json',
82
- ...ANTIGRAVITY_HEADERS,
83
- ...fingerprintHeaders
79
+ ...ANTIGRAVITY_HEADERS
84
80
  };
85
81
 
86
82
  const modelFamily = getModelFamily(model);
@@ -14,9 +14,10 @@ import crypto from 'crypto';
14
14
  * enabling prompt caching (cache is scoped to session + organization).
15
15
  *
16
16
  * @param {Object} anthropicRequest - The Anthropic-format request
17
+ * @param {string} accountEmail - The account email to make session IDs unique per account
17
18
  * @returns {string} A stable session ID (32 hex characters) or random UUID if no user message
18
19
  */
19
- export function deriveSessionId(anthropicRequest) {
20
+ export function deriveSessionId(anthropicRequest, accountEmail) {
20
21
  const messages = anthropicRequest.messages || [];
21
22
 
22
23
  // Find the first user message
@@ -35,8 +36,13 @@ export function deriveSessionId(anthropicRequest) {
35
36
  }
36
37
 
37
38
  if (content) {
39
+ // Include account email in the content to be hashed to ensure
40
+ // unique session IDs per account for the same conversation.
41
+ // This prevents Google from correlating sessions across accounts.
42
+ const saltedContent = accountEmail ? `${accountEmail}:${content}` : content;
43
+
38
44
  // Hash the content with SHA256, return first 32 hex chars
39
- const hash = crypto.createHash('sha256').update(content).digest('hex');
45
+ const hash = crypto.createHash('sha256').update(saltedContent).digest('hex');
40
46
  return hash.substring(0, 32);
41
47
  }
42
48
  }
@@ -139,7 +139,7 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
139
139
  // Get token and project for this account
140
140
  const token = await accountManager.getTokenForAccount(account);
141
141
  const project = await accountManager.getProjectForAccount(account, token);
142
- const payload = buildCloudCodeRequest(anthropicRequest, project);
142
+ const payload = buildCloudCodeRequest(anthropicRequest, project, account.email);
143
143
 
144
144
  logger.debug(`[CloudCode] Starting stream for model: ${model}`);
145
145
 
@@ -155,7 +155,7 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
155
155
 
156
156
  const response = await throttledFetch(url, {
157
157
  method: 'POST',
158
- headers: buildHeaders(token, model, 'text/event-stream', account.fingerprint),
158
+ headers: buildHeaders(token, model, 'text/event-stream'),
159
159
  body: JSON.stringify(payload)
160
160
  });
161
161
 
@@ -336,7 +336,7 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
336
336
  // Refetch the response
337
337
  currentResponse = await throttledFetch(url, {
338
338
  method: 'POST',
339
- headers: buildHeaders(token, model, 'text/event-stream', account.fingerprint),
339
+ headers: buildHeaders(token, model, 'text/event-stream'),
340
340
  body: JSON.stringify(payload)
341
341
  });
342
342
 
@@ -369,7 +369,7 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
369
369
  await sleep(1000);
370
370
  currentResponse = await throttledFetch(url, {
371
371
  method: 'POST',
372
- headers: buildHeaders(token, model, 'text/event-stream', account.fingerprint),
372
+ headers: buildHeaders(token, model, 'text/event-stream'),
373
373
  body: JSON.stringify(payload)
374
374
  });
375
375
  if (currentResponse.ok) {