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/README.md +2 -1
- package/package.json +1 -1
- package/public/css/style.css +1 -1
- package/public/index.html +0 -2
- package/public/js/components/account-manager.js +0 -121
- package/public/views/accounts.html +0 -149
- package/src/account-manager/credentials.js +1 -1
- package/src/account-manager/index.js +1 -85
- package/src/account-manager/storage.js +3 -13
- package/src/cli/accounts.js +2 -4
- package/src/cloudcode/message-handler.js +2 -2
- package/src/cloudcode/model-api.js +8 -10
- package/src/cloudcode/request-builder.js +5 -9
- package/src/cloudcode/session-manager.js +8 -2
- package/src/cloudcode/streaming-handler.js +4 -4
- package/src/constants.js +25 -20
- package/src/server.js +0 -2
- package/src/webui/index.js +0 -72
- package/src/utils/fingerprint.js +0 -133
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
|
package/src/cli/accounts.js
CHANGED
|
@@ -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'
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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'
|
|
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(
|
|
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'
|
|
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'
|
|
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'
|
|
372
|
+
headers: buildHeaders(token, model, 'text/event-stream'),
|
|
373
373
|
body: JSON.stringify(payload)
|
|
374
374
|
});
|
|
375
375
|
if (currentResponse.ok) {
|