antigravity-claude-proxy 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/public/js/components/server-config.js +6 -0
- package/public/js/config/constants.js +4 -0
- package/public/js/data-store.js +1 -19
- package/public/js/store.js +21 -0
- package/public/views/accounts.html +10 -1
- package/public/views/settings.html +22 -0
- package/src/cloudcode/message-handler.js +4 -2
- package/src/cloudcode/streaming-handler.js +4 -2
- package/src/config.js +1 -0
- package/src/server.js +1 -1
- package/src/webui/index.js +10 -2
package/README.md
CHANGED
|
@@ -362,6 +362,7 @@ While most users can use the default settings, you can tune the proxy behavior v
|
|
|
362
362
|
- **Retry Logic**: Configure `maxRetries`, `retryBaseMs`, and `retryMaxMs`.
|
|
363
363
|
- **Load Balancing**: Adjust `defaultCooldownMs` and `maxWaitBeforeErrorMs`.
|
|
364
364
|
- **Persistence**: Enable `persistTokenCache` to save OAuth sessions across restarts.
|
|
365
|
+
- **Max Accounts**: Set `maxAccounts` (1-100) to limit the number of Google accounts. Default: 10.
|
|
365
366
|
|
|
366
367
|
Refer to `config.example.json` for a complete list of fields and documentation.
|
|
367
368
|
|
package/package.json
CHANGED
|
@@ -250,6 +250,12 @@ window.Components.serverConfig = () => ({
|
|
|
250
250
|
(v) => window.Validators.validateTimeout(v, MAX_WAIT_MIN, MAX_WAIT_MAX));
|
|
251
251
|
},
|
|
252
252
|
|
|
253
|
+
toggleMaxAccounts(value) {
|
|
254
|
+
const { MAX_ACCOUNTS_MIN, MAX_ACCOUNTS_MAX } = window.AppConstants.VALIDATION;
|
|
255
|
+
this.saveConfigField('maxAccounts', value, 'Max Accounts',
|
|
256
|
+
(v) => window.Validators.validateRange(v, MAX_ACCOUNTS_MIN, MAX_ACCOUNTS_MAX, 'Max Accounts'));
|
|
257
|
+
},
|
|
258
|
+
|
|
253
259
|
toggleRateLimitDedupWindowMs(value) {
|
|
254
260
|
const { RATE_LIMIT_DEDUP_MIN, RATE_LIMIT_DEDUP_MAX } = window.AppConstants.VALIDATION;
|
|
255
261
|
this.saveConfigField('rateLimitDedupWindowMs', value, 'Rate Limit Dedup Window',
|
|
@@ -69,6 +69,10 @@ window.AppConstants.VALIDATION = {
|
|
|
69
69
|
MAX_WAIT_MIN: 60000,
|
|
70
70
|
MAX_WAIT_MAX: 1800000,
|
|
71
71
|
|
|
72
|
+
// Max accounts range (1 - 100)
|
|
73
|
+
MAX_ACCOUNTS_MIN: 1,
|
|
74
|
+
MAX_ACCOUNTS_MAX: 100,
|
|
75
|
+
|
|
72
76
|
// Rate limit dedup window (1 - 30 seconds)
|
|
73
77
|
RATE_LIMIT_DEDUP_MIN: 1000,
|
|
74
78
|
RATE_LIMIT_DEDUP_MAX: 30000,
|
package/public/js/data-store.js
CHANGED
|
@@ -13,6 +13,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
13
13
|
modelConfig: {}, // Model metadata (hidden, pinned, alias)
|
|
14
14
|
quotaRows: [], // Filtered view
|
|
15
15
|
usageHistory: {}, // Usage statistics history (from /account-limits?includeHistory=true)
|
|
16
|
+
maxAccounts: 10, // Maximum number of accounts allowed (from config)
|
|
16
17
|
loading: false,
|
|
17
18
|
initialLoad: true, // Track first load for skeleton screen
|
|
18
19
|
connectionStatus: 'connecting',
|
|
@@ -125,11 +126,6 @@ document.addEventListener('alpine:init', () => {
|
|
|
125
126
|
this.computeQuotaRows();
|
|
126
127
|
|
|
127
128
|
this.lastUpdated = new Date().toLocaleTimeString();
|
|
128
|
-
|
|
129
|
-
// Fetch version from config endpoint if not already loaded
|
|
130
|
-
if (this.initialLoad) {
|
|
131
|
-
this.fetchVersion(password);
|
|
132
|
-
}
|
|
133
129
|
} catch (error) {
|
|
134
130
|
console.error('Fetch error:', error);
|
|
135
131
|
const store = Alpine.store('global');
|
|
@@ -140,20 +136,6 @@ document.addEventListener('alpine:init', () => {
|
|
|
140
136
|
}
|
|
141
137
|
},
|
|
142
138
|
|
|
143
|
-
async fetchVersion(password) {
|
|
144
|
-
try {
|
|
145
|
-
const { response } = await window.utils.request('/api/config', {}, password);
|
|
146
|
-
if (response.ok) {
|
|
147
|
-
const data = await response.json();
|
|
148
|
-
if (data.version) {
|
|
149
|
-
Alpine.store('global').version = data.version;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} catch (error) {
|
|
153
|
-
console.warn('Failed to fetch version:', error);
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
|
|
157
139
|
async performHealthCheck() {
|
|
158
140
|
try {
|
|
159
141
|
// Get password from global store
|
package/public/js/store.js
CHANGED
|
@@ -30,6 +30,27 @@ document.addEventListener('alpine:init', () => {
|
|
|
30
30
|
this.activeTab = hash;
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
// 4. Fetch version from API
|
|
35
|
+
this.fetchVersion();
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async fetchVersion() {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch('/api/config');
|
|
41
|
+
if (response.ok) {
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
if (data.version) {
|
|
44
|
+
this.version = data.version;
|
|
45
|
+
}
|
|
46
|
+
// Update maxAccounts in data store
|
|
47
|
+
if (data.config && typeof data.config.maxAccounts === 'number') {
|
|
48
|
+
Alpine.store('data').maxAccounts = data.config.maxAccounts;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.debug('Could not fetch version:', error);
|
|
53
|
+
}
|
|
33
54
|
},
|
|
34
55
|
|
|
35
56
|
// App State
|
|
@@ -39,8 +39,17 @@
|
|
|
39
39
|
</svg>
|
|
40
40
|
<span x-text="$store.global.t('reload')">Reload</span>
|
|
41
41
|
</button>
|
|
42
|
+
<!-- Account Count Indicator -->
|
|
43
|
+
<div class="flex items-center h-6 px-2 rounded bg-space-800/80 border border-space-border/50"
|
|
44
|
+
:class="{ 'border-yellow-500/50': $store.data.accounts.length >= $store.data.maxAccounts }">
|
|
45
|
+
<span class="text-[11px] font-mono"
|
|
46
|
+
:class="$store.data.accounts.length >= $store.data.maxAccounts ? 'text-yellow-400' : 'text-gray-400'"
|
|
47
|
+
x-text="$store.data.accounts.length + '/' + $store.data.maxAccounts"></span>
|
|
48
|
+
</div>
|
|
42
49
|
<button class="btn bg-neon-purple hover:bg-purple-600 border-none text-white btn-xs gap-2 shadow-lg shadow-neon-purple/20 h-8"
|
|
43
|
-
|
|
50
|
+
:class="{ 'opacity-50 cursor-not-allowed': $store.data.accounts.length >= $store.data.maxAccounts }"
|
|
51
|
+
:disabled="$store.data.accounts.length >= $store.data.maxAccounts"
|
|
52
|
+
@click="$store.data.accounts.length < $store.data.maxAccounts && document.getElementById('add_account_modal').showModal()">
|
|
44
53
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
45
54
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
46
55
|
</svg>
|
|
@@ -934,6 +934,28 @@
|
|
|
934
934
|
</label>
|
|
935
935
|
</div>
|
|
936
936
|
</div>
|
|
937
|
+
|
|
938
|
+
<!-- Max Accounts -->
|
|
939
|
+
<div class="form-control view-card border-space-border/50 hover:border-neon-cyan/50">
|
|
940
|
+
<label class="label pt-0">
|
|
941
|
+
<span class="label-text text-gray-400 text-xs">Max Accounts</span>
|
|
942
|
+
<span class="label-text-alt font-mono text-neon-cyan text-xs font-semibold"
|
|
943
|
+
x-text="serverConfig.maxAccounts || 10"></span>
|
|
944
|
+
</label>
|
|
945
|
+
<div class="flex gap-3 items-center">
|
|
946
|
+
<input type="range" min="1" max="100" class="custom-range custom-range-cyan flex-1"
|
|
947
|
+
:value="serverConfig.maxAccounts || 10"
|
|
948
|
+
:style="`background-size: ${((serverConfig.maxAccounts || 10) - 1) / 99 * 100}% 100%`"
|
|
949
|
+
@input="toggleMaxAccounts($event.target.value)"
|
|
950
|
+
aria-label="Max accounts slider">
|
|
951
|
+
<input type="number" min="1" max="100"
|
|
952
|
+
class="input input-xs input-bordered w-16 bg-space-800 border-space-border text-white font-mono text-center"
|
|
953
|
+
:value="serverConfig.maxAccounts || 10"
|
|
954
|
+
@change="toggleMaxAccounts($event.target.value)"
|
|
955
|
+
aria-label="Max accounts value">
|
|
956
|
+
</div>
|
|
957
|
+
<span class="text-[11px] text-gray-500 mt-1">Maximum number of Google accounts allowed</span>
|
|
958
|
+
</div>
|
|
937
959
|
</div>
|
|
938
960
|
|
|
939
961
|
<!-- 🔀 Account Selection Strategy -->
|
|
@@ -279,8 +279,10 @@ export async function sendMessage(anthropicRequest, accountManager, fallbackEnab
|
|
|
279
279
|
|
|
280
280
|
if (response.status >= 400) {
|
|
281
281
|
lastError = new Error(`API error ${response.status}: ${errorText}`);
|
|
282
|
-
//
|
|
283
|
-
if (response.status
|
|
282
|
+
// Try next endpoint for 403/404/5xx errors (matches opencode-antigravity-auth behavior)
|
|
283
|
+
if (response.status === 403 || response.status === 404) {
|
|
284
|
+
logger.warn(`[CloudCode] ${response.status} at ${endpoint}...`);
|
|
285
|
+
} else if (response.status >= 500) {
|
|
284
286
|
logger.warn(`[CloudCode] ${response.status} error, waiting 1s before retry...`);
|
|
285
287
|
await sleep(1000);
|
|
286
288
|
}
|
|
@@ -274,8 +274,10 @@ export async function* sendMessageStream(anthropicRequest, accountManager, fallb
|
|
|
274
274
|
|
|
275
275
|
lastError = new Error(`API error ${response.status}: ${errorText}`);
|
|
276
276
|
|
|
277
|
-
//
|
|
278
|
-
if (response.status
|
|
277
|
+
// Try next endpoint for 403/404/5xx errors (matches opencode-antigravity-auth behavior)
|
|
278
|
+
if (response.status === 403 || response.status === 404) {
|
|
279
|
+
logger.warn(`[CloudCode] ${response.status} at ${endpoint}..`);
|
|
280
|
+
} else if (response.status >= 500) {
|
|
279
281
|
logger.warn(`[CloudCode] ${response.status} stream error, waiting 1s before retry...`);
|
|
280
282
|
await sleep(1000);
|
|
281
283
|
}
|
package/src/config.js
CHANGED
|
@@ -15,6 +15,7 @@ const DEFAULT_CONFIG = {
|
|
|
15
15
|
persistTokenCache: false,
|
|
16
16
|
defaultCooldownMs: 10000, // 10 seconds
|
|
17
17
|
maxWaitBeforeErrorMs: 120000, // 2 minutes
|
|
18
|
+
maxAccounts: 10, // Maximum number of accounts allowed
|
|
18
19
|
modelMapping: {},
|
|
19
20
|
// Account selection strategy configuration
|
|
20
21
|
accountSelection: {
|
package/src/server.js
CHANGED
|
@@ -638,7 +638,7 @@ app.post('/refresh-token', async (req, res) => {
|
|
|
638
638
|
app.get('/v1/models', async (req, res) => {
|
|
639
639
|
try {
|
|
640
640
|
await ensureInitialized();
|
|
641
|
-
const account = accountManager.
|
|
641
|
+
const { account } = accountManager.selectAccount();
|
|
642
642
|
if (!account) {
|
|
643
643
|
return res.status(503).json({
|
|
644
644
|
type: 'error',
|
package/src/webui/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { readFileSync } from 'fs';
|
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
import express from 'express';
|
|
19
19
|
import { getPublicConfig, saveConfig, config } from '../config.js';
|
|
20
|
-
import { DEFAULT_PORT, ACCOUNT_CONFIG_PATH } from '../constants.js';
|
|
20
|
+
import { DEFAULT_PORT, ACCOUNT_CONFIG_PATH, MAX_ACCOUNTS } from '../constants.js';
|
|
21
21
|
import { readClaudeConfig, updateClaudeConfig, replaceClaudeConfig, getClaudeConfigPath, readPresets, savePreset, deletePreset } from '../utils/claude-config.js';
|
|
22
22
|
import { logger } from '../utils/logger.js';
|
|
23
23
|
import { getAuthorizationUrl, completeOAuthFlow, startCallbackServer } from '../auth/oauth.js';
|
|
@@ -77,6 +77,7 @@ async function removeAccount(email) {
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Add new account to config
|
|
80
|
+
* @throws {Error} If MAX_ACCOUNTS limit is reached (for new accounts only)
|
|
80
81
|
*/
|
|
81
82
|
async function addAccount(accountData) {
|
|
82
83
|
const { accounts, settings, activeIndex } = await loadAccounts(ACCOUNT_CONFIG_PATH);
|
|
@@ -95,6 +96,10 @@ async function addAccount(accountData) {
|
|
|
95
96
|
};
|
|
96
97
|
logger.info(`[WebUI] Account ${accountData.email} updated`);
|
|
97
98
|
} else {
|
|
99
|
+
// Check MAX_ACCOUNTS limit before adding new account
|
|
100
|
+
if (accounts.length >= MAX_ACCOUNTS) {
|
|
101
|
+
throw new Error(`Maximum of ${MAX_ACCOUNTS} accounts reached. Update maxAccounts in config to increase the limit.`);
|
|
102
|
+
}
|
|
98
103
|
// Add new account
|
|
99
104
|
accounts.push({
|
|
100
105
|
...accountData,
|
|
@@ -282,7 +287,7 @@ export function mountWebUI(app, dirname, accountManager) {
|
|
|
282
287
|
*/
|
|
283
288
|
app.post('/api/config', (req, res) => {
|
|
284
289
|
try {
|
|
285
|
-
const { debug, logLevel, maxRetries, retryBaseMs, retryMaxMs, persistTokenCache, defaultCooldownMs, maxWaitBeforeErrorMs, accountSelection } = req.body;
|
|
290
|
+
const { debug, logLevel, maxRetries, retryBaseMs, retryMaxMs, persistTokenCache, defaultCooldownMs, maxWaitBeforeErrorMs, maxAccounts, accountSelection } = req.body;
|
|
286
291
|
|
|
287
292
|
// Only allow updating specific fields (security)
|
|
288
293
|
const updates = {};
|
|
@@ -308,6 +313,9 @@ export function mountWebUI(app, dirname, accountManager) {
|
|
|
308
313
|
if (typeof maxWaitBeforeErrorMs === 'number' && maxWaitBeforeErrorMs >= 0 && maxWaitBeforeErrorMs <= 600000) {
|
|
309
314
|
updates.maxWaitBeforeErrorMs = maxWaitBeforeErrorMs;
|
|
310
315
|
}
|
|
316
|
+
if (typeof maxAccounts === 'number' && maxAccounts >= 1 && maxAccounts <= 100) {
|
|
317
|
+
updates.maxAccounts = maxAccounts;
|
|
318
|
+
}
|
|
311
319
|
// Account selection strategy validation
|
|
312
320
|
if (accountSelection && typeof accountSelection === 'object') {
|
|
313
321
|
const validStrategies = ['sticky', 'round-robin', 'hybrid'];
|