antigravity-claude-proxy 2.4.1 → 2.4.2
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 +60 -4
- package/package.json +1 -1
- package/public/index.html +2 -1
- package/public/js/components/dashboard/charts.js +17 -16
- package/public/js/components/dashboard/filters.js +2 -2
- package/public/js/components/logs-viewer.js +2 -2
- package/public/js/data-store.js +7 -4
- package/public/js/utils/ui-logger.js +143 -0
- package/src/auth/oauth.js +86 -12
- package/src/constants.js +9 -3
package/README.md
CHANGED
|
@@ -154,15 +154,13 @@ Add this configuration:
|
|
|
154
154
|
"ANTHROPIC_MODEL": "claude-opus-4-5-thinking",
|
|
155
155
|
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-5-thinking",
|
|
156
156
|
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-5-thinking",
|
|
157
|
-
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "
|
|
157
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-sonnet-4-5",
|
|
158
158
|
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-5-thinking",
|
|
159
159
|
"ENABLE_EXPERIMENTAL_MCP_CLI": "true"
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
(Please use **gemini-2.5-flash-lite** as the default haiku model, even if others are claude, as claude code makes several calls via the haiku model for background tasks. If you use claude model for it, you may use you claude usage sooner)
|
|
165
|
-
|
|
166
164
|
Or to use Gemini models:
|
|
167
165
|
|
|
168
166
|
```json
|
|
@@ -173,7 +171,7 @@ Or to use Gemini models:
|
|
|
173
171
|
"ANTHROPIC_MODEL": "gemini-3-pro-high[1m]",
|
|
174
172
|
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gemini-3-pro-high[1m]",
|
|
175
173
|
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gemini-3-flash[1m]",
|
|
176
|
-
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-
|
|
174
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-3-flash[1m]",
|
|
177
175
|
"CLAUDE_CODE_SUBAGENT_MODEL": "gemini-3-flash[1m]",
|
|
178
176
|
"ENABLE_EXPERIMENTAL_MCP_CLI": "true"
|
|
179
177
|
}
|
|
@@ -435,6 +433,64 @@ npm run test:cache-control # Cache control field stripping
|
|
|
435
433
|
|
|
436
434
|
## Troubleshooting
|
|
437
435
|
|
|
436
|
+
### Windows: OAuth Port Error (EACCES)
|
|
437
|
+
|
|
438
|
+
On Windows, the default OAuth callback port (51121) may be reserved by Hyper-V, WSL2, or Docker. If you see:
|
|
439
|
+
|
|
440
|
+
```
|
|
441
|
+
Error: listen EACCES: permission denied 0.0.0.0:51121
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
The proxy will automatically try fallback ports (51122-51126). If all ports fail, try these solutions:
|
|
445
|
+
|
|
446
|
+
#### Option 1: Use a Custom Port (Recommended)
|
|
447
|
+
|
|
448
|
+
Set a custom port outside the reserved range:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
# Windows PowerShell
|
|
452
|
+
$env:OAUTH_CALLBACK_PORT = "3456"
|
|
453
|
+
antigravity-claude-proxy start
|
|
454
|
+
|
|
455
|
+
# Windows CMD
|
|
456
|
+
set OAUTH_CALLBACK_PORT=3456
|
|
457
|
+
antigravity-claude-proxy start
|
|
458
|
+
|
|
459
|
+
# Or add to your .env file
|
|
460
|
+
OAUTH_CALLBACK_PORT=3456
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### Option 2: Reset Windows NAT
|
|
464
|
+
|
|
465
|
+
Run as Administrator:
|
|
466
|
+
|
|
467
|
+
```powershell
|
|
468
|
+
net stop winnat
|
|
469
|
+
net start winnat
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### Option 3: Check Reserved Ports
|
|
473
|
+
|
|
474
|
+
See which ports are reserved:
|
|
475
|
+
|
|
476
|
+
```powershell
|
|
477
|
+
netsh interface ipv4 show excludedportrange protocol=tcp
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
If 51121 is in a reserved range, use Option 1 with a port outside those ranges.
|
|
481
|
+
|
|
482
|
+
#### Option 4: Permanently Exclude Port (Admin)
|
|
483
|
+
|
|
484
|
+
Reserve the port before Hyper-V claims it (run as Administrator):
|
|
485
|
+
|
|
486
|
+
```powershell
|
|
487
|
+
netsh int ipv4 add excludedportrange protocol=tcp startport=51121 numberofports=1
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
> **Note:** The server automatically tries fallback ports (51122-51126) if the primary port fails.
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
438
494
|
### "Could not extract token from Antigravity"
|
|
439
495
|
|
|
440
496
|
If using single-account mode with Antigravity:
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
<body
|
|
20
20
|
class="bg-space-950 text-gray-300 font-sans antialiased min-h-screen overflow-hidden selection:bg-neon-purple selection:text-white"
|
|
21
|
-
x-cloak x-data="app" x-init="
|
|
21
|
+
x-cloak x-data="app" x-init="if(window.UILogger) window.UILogger.debug('App initialized')">
|
|
22
22
|
|
|
23
23
|
<!-- Toast Notification -->
|
|
24
24
|
<div class="fixed top-4 right-4 z-[100] flex flex-col gap-2 pointer-events-none">
|
|
@@ -389,6 +389,7 @@
|
|
|
389
389
|
<!-- Scripts - Loading Order Matters! -->
|
|
390
390
|
<!-- 1. Config & Utils (global helpers) -->
|
|
391
391
|
<script src="js/config/constants.js"></script>
|
|
392
|
+
<script src="js/utils/ui-logger.js"></script><!-- Issue #183: Conditional logging utility -->
|
|
392
393
|
<script src="js/utils.js"></script>
|
|
393
394
|
<script src="js/utils/error-handler.js"></script>
|
|
394
395
|
<script src="js/utils/account-actions.js"></script>
|
|
@@ -106,7 +106,7 @@ window.DashboardCharts.createDataset = function (label, data, color, canvas) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
} catch (e) {
|
|
109
|
-
|
|
109
|
+
if (window.UILogger) window.UILogger.debug("Gradient fallback:", e.message);
|
|
110
110
|
gradient = null;
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -149,7 +149,7 @@ window.DashboardCharts.updateCharts = function (component) {
|
|
|
149
149
|
console.debug("Destroying existing quota chart from canvas property");
|
|
150
150
|
try {
|
|
151
151
|
canvas._chartInstance.destroy();
|
|
152
|
-
} catch(e) {
|
|
152
|
+
} catch(e) { if (window.UILogger) window.UILogger.debug(e); }
|
|
153
153
|
canvas._chartInstance = null;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -170,11 +170,11 @@ window.DashboardCharts.updateCharts = function (component) {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
if (typeof Chart === "undefined") {
|
|
173
|
-
|
|
173
|
+
if (window.UILogger) window.UILogger.warn("Chart.js not loaded");
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
176
|
if (!isCanvasReady(canvas)) {
|
|
177
|
-
|
|
177
|
+
if (window.UILogger) window.UILogger.debug("quotaChart canvas not ready, skipping update");
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -319,12 +319,13 @@ window.DashboardCharts.updateCharts = function (component) {
|
|
|
319
319
|
window.DashboardCharts.updateTrendChart = function (component) {
|
|
320
320
|
// Prevent concurrent updates (fixes race condition on rapid toggling)
|
|
321
321
|
if (_trendChartUpdateLock) {
|
|
322
|
-
|
|
322
|
+
if (window.UILogger) window.UILogger.debug("[updateTrendChart] Update already in progress, skipping");
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
325
|
_trendChartUpdateLock = true;
|
|
326
326
|
|
|
327
|
-
|
|
327
|
+
const logger = window.UILogger || console;
|
|
328
|
+
logger.debug("[updateTrendChart] Starting update...");
|
|
328
329
|
|
|
329
330
|
const canvas = document.getElementById("usageTrendChart");
|
|
330
331
|
|
|
@@ -335,7 +336,7 @@ window.DashboardCharts.updateTrendChart = function (component) {
|
|
|
335
336
|
try {
|
|
336
337
|
canvas._chartInstance.stop();
|
|
337
338
|
canvas._chartInstance.destroy();
|
|
338
|
-
} catch(e) {
|
|
339
|
+
} catch(e) { if (window.UILogger) window.UILogger.debug(e); }
|
|
339
340
|
canvas._chartInstance = null;
|
|
340
341
|
}
|
|
341
342
|
|
|
@@ -359,17 +360,17 @@ window.DashboardCharts.updateTrendChart = function (component) {
|
|
|
359
360
|
|
|
360
361
|
// Safety checks
|
|
361
362
|
if (!canvas) {
|
|
362
|
-
|
|
363
|
-
_trendChartUpdateLock = false;
|
|
363
|
+
if (window.UILogger) window.UILogger.debug("[updateTrendChart] Canvas not found in DOM");
|
|
364
|
+
_trendChartUpdateLock = false;
|
|
364
365
|
return;
|
|
365
366
|
}
|
|
366
367
|
if (typeof Chart === "undefined") {
|
|
367
|
-
|
|
368
|
-
_trendChartUpdateLock = false;
|
|
368
|
+
if (window.UILogger) window.UILogger.warn("[updateTrendChart] Chart.js not loaded");
|
|
369
|
+
_trendChartUpdateLock = false;
|
|
369
370
|
return;
|
|
370
371
|
}
|
|
371
372
|
|
|
372
|
-
|
|
373
|
+
if (window.UILogger) window.UILogger.debug("[updateTrendChart] Canvas element:", {
|
|
373
374
|
exists: !!canvas,
|
|
374
375
|
isConnected: canvas.isConnected,
|
|
375
376
|
width: canvas.offsetWidth,
|
|
@@ -378,7 +379,7 @@ window.DashboardCharts.updateTrendChart = function (component) {
|
|
|
378
379
|
});
|
|
379
380
|
|
|
380
381
|
if (!isCanvasReady(canvas)) {
|
|
381
|
-
|
|
382
|
+
if (window.UILogger) window.UILogger.debug("[updateTrendChart] Canvas not ready", {
|
|
382
383
|
isConnected: canvas.isConnected,
|
|
383
384
|
width: canvas.offsetWidth,
|
|
384
385
|
height: canvas.offsetHeight,
|
|
@@ -394,17 +395,17 @@ window.DashboardCharts.updateTrendChart = function (component) {
|
|
|
394
395
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
395
396
|
}
|
|
396
397
|
} catch (e) {
|
|
397
|
-
|
|
398
|
+
if (window.UILogger) window.UILogger.debug("[updateTrendChart] Failed to clear canvas:", e.message);
|
|
398
399
|
}
|
|
399
400
|
|
|
400
|
-
|
|
401
|
+
if (window.UILogger) window.UILogger.debug(
|
|
401
402
|
"[updateTrendChart] Canvas is ready, proceeding with chart creation"
|
|
402
403
|
);
|
|
403
404
|
|
|
404
405
|
// Use filtered history data based on time range
|
|
405
406
|
const history = window.DashboardFilters.getFilteredHistoryData(component);
|
|
406
407
|
if (!history || Object.keys(history).length === 0) {
|
|
407
|
-
|
|
408
|
+
if (window.UILogger) window.UILogger.debug("No history data available for trend chart (after filtering)");
|
|
408
409
|
component.hasFilteredTrendData = false;
|
|
409
410
|
_trendChartUpdateLock = false;
|
|
410
411
|
return;
|
|
@@ -50,7 +50,7 @@ window.DashboardFilters.loadPreferences = function(component) {
|
|
|
50
50
|
component.selectedModels = prefs.selectedModels || {};
|
|
51
51
|
}
|
|
52
52
|
} catch (e) {
|
|
53
|
-
|
|
53
|
+
if (window.UILogger) window.UILogger.debug('Failed to load dashboard preferences:', e.message);
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
|
|
@@ -67,7 +67,7 @@ window.DashboardFilters.savePreferences = function(component) {
|
|
|
67
67
|
selectedModels: component.selectedModels
|
|
68
68
|
}));
|
|
69
69
|
} catch (e) {
|
|
70
|
-
|
|
70
|
+
if (window.UILogger) window.UILogger.debug('Failed to save dashboard preferences:', e.message);
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
|
|
@@ -79,12 +79,12 @@ window.Components.logsViewer = () => ({
|
|
|
79
79
|
this.$nextTick(() => this.scrollToBottom());
|
|
80
80
|
}
|
|
81
81
|
} catch (e) {
|
|
82
|
-
|
|
82
|
+
if (window.UILogger) window.UILogger.debug('Log parse error:', e.message);
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
this.eventSource.onerror = () => {
|
|
87
|
-
|
|
87
|
+
if (window.UILogger) window.UILogger.debug('Log stream disconnected, reconnecting...');
|
|
88
88
|
setTimeout(() => this.startLogStream(), 3000);
|
|
89
89
|
};
|
|
90
90
|
},
|
package/public/js/data-store.js
CHANGED
|
@@ -55,7 +55,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
55
55
|
|
|
56
56
|
// Check TTL
|
|
57
57
|
if (data.timestamp && (Date.now() - data.timestamp > CACHE_TTL)) {
|
|
58
|
-
|
|
58
|
+
if (window.UILogger) window.UILogger.debug('Cache expired, skipping restoration');
|
|
59
59
|
localStorage.removeItem('ag_data_cache');
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
@@ -70,11 +70,11 @@ document.addEventListener('alpine:init', () => {
|
|
|
70
70
|
// Don't show loading on initial load if we have cache
|
|
71
71
|
this.initialLoad = false;
|
|
72
72
|
this.computeQuotaRows();
|
|
73
|
-
|
|
73
|
+
if (window.UILogger) window.UILogger.debug('Restored data from cache');
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
} catch (e) {
|
|
77
|
-
|
|
77
|
+
if (window.UILogger) window.UILogger.debug('Failed to load cache', e.message);
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
|
|
@@ -89,7 +89,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
89
89
|
};
|
|
90
90
|
localStorage.setItem('ag_data_cache', JSON.stringify(cacheData));
|
|
91
91
|
} catch (e) {
|
|
92
|
-
|
|
92
|
+
if (window.UILogger) window.UILogger.debug('Failed to save cache', e.message);
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
95
|
|
|
@@ -127,6 +127,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
127
127
|
|
|
128
128
|
this.lastUpdated = new Date().toLocaleTimeString();
|
|
129
129
|
} catch (error) {
|
|
130
|
+
// Keep error logging for actual fetch failures
|
|
130
131
|
console.error('Fetch error:', error);
|
|
131
132
|
const store = Alpine.store('global');
|
|
132
133
|
store.showToast(store.t('connectionLost'), 'error');
|
|
@@ -237,6 +238,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
237
238
|
let minResetTime = null;
|
|
238
239
|
|
|
239
240
|
this.accounts.forEach(acc => {
|
|
241
|
+
if (acc.enabled === false) return;
|
|
240
242
|
if (this.filters.account !== 'all' && acc.email !== this.filters.account) return;
|
|
241
243
|
|
|
242
244
|
const limit = acc.limits?.[modelId];
|
|
@@ -352,6 +354,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
352
354
|
const quotaInfo = [];
|
|
353
355
|
// Use ALL accounts (no account filter)
|
|
354
356
|
this.accounts.forEach(acc => {
|
|
357
|
+
if (acc.enabled === false) return;
|
|
355
358
|
const limit = acc.limits?.[modelId];
|
|
356
359
|
if (!limit) return;
|
|
357
360
|
const pct = limit.remainingFraction !== null ? Math.round(limit.remainingFraction * 100) : 0;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Logger Utility
|
|
3
|
+
* Provides conditional logging for the web UI to reduce console spam in production.
|
|
4
|
+
* Wraps console methods and only outputs when debug mode is enabled.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* window.UILogger.debug('message') - Only logs if debug mode enabled
|
|
8
|
+
* window.UILogger.info('message') - Only logs if debug mode enabled
|
|
9
|
+
* window.UILogger.warn('message') - Always logs (important warnings)
|
|
10
|
+
* window.UILogger.error('message') - Always logs (errors should always be visible)
|
|
11
|
+
*
|
|
12
|
+
* Enable debug mode:
|
|
13
|
+
* - Set localStorage.setItem('ag_debug', 'true') in browser console
|
|
14
|
+
* - Or pass ?debug=true in URL
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
(function() {
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
// Check if debug mode is enabled
|
|
21
|
+
function isDebugEnabled() {
|
|
22
|
+
// Check URL parameter
|
|
23
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
24
|
+
if (urlParams.get('debug') === 'true') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check localStorage
|
|
29
|
+
try {
|
|
30
|
+
return localStorage.getItem('ag_debug') === 'true';
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Cache debug state (can be refreshed)
|
|
37
|
+
let debugEnabled = isDebugEnabled();
|
|
38
|
+
|
|
39
|
+
window.UILogger = {
|
|
40
|
+
/**
|
|
41
|
+
* Refresh debug state (call after changing localStorage)
|
|
42
|
+
*/
|
|
43
|
+
refresh() {
|
|
44
|
+
debugEnabled = isDebugEnabled();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Enable debug mode
|
|
49
|
+
*/
|
|
50
|
+
enableDebug() {
|
|
51
|
+
try {
|
|
52
|
+
localStorage.setItem('ag_debug', 'true');
|
|
53
|
+
debugEnabled = true;
|
|
54
|
+
console.info('[UILogger] Debug mode enabled. Refresh page to see all logs.');
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.warn('[UILogger] Could not save debug preference');
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Disable debug mode
|
|
62
|
+
*/
|
|
63
|
+
disableDebug() {
|
|
64
|
+
try {
|
|
65
|
+
localStorage.removeItem('ag_debug');
|
|
66
|
+
debugEnabled = false;
|
|
67
|
+
console.info('[UILogger] Debug mode disabled.');
|
|
68
|
+
} catch (e) {
|
|
69
|
+
// Ignore
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if debug mode is enabled
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
isDebug() {
|
|
78
|
+
return debugEnabled;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Debug level - only logs if debug mode enabled
|
|
83
|
+
* Use for verbose debugging info (chart updates, cache operations, etc.)
|
|
84
|
+
*/
|
|
85
|
+
debug(...args) {
|
|
86
|
+
if (debugEnabled) {
|
|
87
|
+
console.log('[DEBUG]', ...args);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Info level - only logs if debug mode enabled
|
|
93
|
+
* Use for informational messages that aren't errors
|
|
94
|
+
*/
|
|
95
|
+
info(...args) {
|
|
96
|
+
if (debugEnabled) {
|
|
97
|
+
console.info('[INFO]', ...args);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Log level - alias for debug
|
|
103
|
+
*/
|
|
104
|
+
log(...args) {
|
|
105
|
+
if (debugEnabled) {
|
|
106
|
+
console.log(...args);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Warn level - always logs
|
|
112
|
+
* Use for important warnings that users should see
|
|
113
|
+
* But suppress noisy/expected warnings unless in debug mode
|
|
114
|
+
*/
|
|
115
|
+
warn(...args) {
|
|
116
|
+
// In production, only show critical warnings
|
|
117
|
+
// In debug mode, show all warnings
|
|
118
|
+
if (debugEnabled) {
|
|
119
|
+
console.warn(...args);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Warn level that always shows (for critical warnings)
|
|
125
|
+
*/
|
|
126
|
+
warnAlways(...args) {
|
|
127
|
+
console.warn(...args);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Error level - always logs
|
|
132
|
+
* Errors should always be visible for debugging
|
|
133
|
+
*/
|
|
134
|
+
error(...args) {
|
|
135
|
+
console.error(...args);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Log initial state (only in debug mode)
|
|
140
|
+
if (debugEnabled) {
|
|
141
|
+
console.info('[UILogger] Debug mode is ON. Set localStorage ag_debug=false to disable.');
|
|
142
|
+
}
|
|
143
|
+
})();
|
package/src/auth/oauth.js
CHANGED
|
@@ -137,22 +137,50 @@ export function extractCodeFromInput(input) {
|
|
|
137
137
|
return { code: trimmed, state: null };
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Attempt to bind server to a specific port
|
|
142
|
+
* @param {http.Server} server - HTTP server instance
|
|
143
|
+
* @param {number} port - Port to bind to
|
|
144
|
+
* @returns {Promise<number>} Resolves with port on success, rejects on error
|
|
145
|
+
*/
|
|
146
|
+
function tryBindPort(server, port) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const onError = (err) => {
|
|
149
|
+
server.removeListener('listening', onSuccess);
|
|
150
|
+
reject(err);
|
|
151
|
+
};
|
|
152
|
+
const onSuccess = () => {
|
|
153
|
+
server.removeListener('error', onError);
|
|
154
|
+
resolve(port);
|
|
155
|
+
};
|
|
156
|
+
server.once('error', onError);
|
|
157
|
+
server.once('listening', onSuccess);
|
|
158
|
+
server.listen(port);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
140
162
|
/**
|
|
141
163
|
* Start a local server to receive the OAuth callback
|
|
164
|
+
* Implements automatic port fallback for Windows compatibility (issue #176)
|
|
142
165
|
* Returns an object with a promise and an abort function
|
|
143
166
|
*
|
|
144
167
|
* @param {string} expectedState - Expected state parameter for CSRF protection
|
|
145
168
|
* @param {number} timeoutMs - Timeout in milliseconds (default 120000)
|
|
146
|
-
* @returns {{promise: Promise<string>, abort: Function}} Object with promise and
|
|
169
|
+
* @returns {{promise: Promise<string>, abort: Function, getPort: Function}} Object with promise, abort, and getPort functions
|
|
147
170
|
*/
|
|
148
171
|
export function startCallbackServer(expectedState, timeoutMs = 120000) {
|
|
149
172
|
let server = null;
|
|
150
173
|
let timeoutId = null;
|
|
151
174
|
let isAborted = false;
|
|
175
|
+
let actualPort = OAUTH_CONFIG.callbackPort;
|
|
176
|
+
|
|
177
|
+
const promise = new Promise(async (resolve, reject) => {
|
|
178
|
+
// Build list of ports to try: primary + fallbacks
|
|
179
|
+
const portsToTry = [OAUTH_CONFIG.callbackPort, ...(OAUTH_CONFIG.callbackFallbackPorts || [])];
|
|
180
|
+
const errors = [];
|
|
152
181
|
|
|
153
|
-
const promise = new Promise((resolve, reject) => {
|
|
154
182
|
server = http.createServer((req, res) => {
|
|
155
|
-
const url = new URL(req.url, `http://localhost:${
|
|
183
|
+
const url = new URL(req.url, `http://localhost:${actualPort}`);
|
|
156
184
|
|
|
157
185
|
if (url.pathname !== '/oauth-callback') {
|
|
158
186
|
res.writeHead(404);
|
|
@@ -232,17 +260,60 @@ export function startCallbackServer(expectedState, timeoutMs = 120000) {
|
|
|
232
260
|
resolve(code);
|
|
233
261
|
});
|
|
234
262
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
263
|
+
// Try ports with fallback logic (issue #176 - Windows EACCES fix)
|
|
264
|
+
let boundSuccessfully = false;
|
|
265
|
+
for (const port of portsToTry) {
|
|
266
|
+
try {
|
|
267
|
+
await tryBindPort(server, port);
|
|
268
|
+
actualPort = port;
|
|
269
|
+
boundSuccessfully = true;
|
|
270
|
+
|
|
271
|
+
if (port !== OAUTH_CONFIG.callbackPort) {
|
|
272
|
+
logger.warn(`[OAuth] Primary port ${OAUTH_CONFIG.callbackPort} unavailable, using fallback port ${port}`);
|
|
273
|
+
} else {
|
|
274
|
+
logger.info(`[OAuth] Callback server listening on port ${port}`);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
} catch (err) {
|
|
278
|
+
const errMsg = err.code === 'EACCES'
|
|
279
|
+
? `Permission denied on port ${port}`
|
|
280
|
+
: err.code === 'EADDRINUSE'
|
|
281
|
+
? `Port ${port} already in use`
|
|
282
|
+
: `Failed to bind port ${port}: ${err.message}`;
|
|
283
|
+
errors.push(errMsg);
|
|
284
|
+
logger.warn(`[OAuth] ${errMsg}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!boundSuccessfully) {
|
|
289
|
+
// All ports failed - provide helpful error message
|
|
290
|
+
const isWindows = process.platform === 'win32';
|
|
291
|
+
let errorMsg = `Failed to start OAuth callback server.\nTried ports: ${portsToTry.join(', ')}\n\nErrors:\n${errors.join('\n')}`;
|
|
292
|
+
|
|
293
|
+
if (isWindows) {
|
|
294
|
+
errorMsg += `\n
|
|
295
|
+
================== WINDOWS TROUBLESHOOTING ==================
|
|
296
|
+
The default port range may be reserved by Hyper-V/WSL2/Docker.
|
|
297
|
+
|
|
298
|
+
Option 1: Use a custom port
|
|
299
|
+
Set OAUTH_CALLBACK_PORT=3456 in your environment or .env file
|
|
300
|
+
|
|
301
|
+
Option 2: Reset Windows NAT (run as Administrator)
|
|
302
|
+
net stop winnat && net start winnat
|
|
303
|
+
|
|
304
|
+
Option 3: Check reserved port ranges
|
|
305
|
+
netsh interface ipv4 show excludedportrange protocol=tcp
|
|
306
|
+
|
|
307
|
+
Option 4: Exclude port from reservation (run as Administrator)
|
|
308
|
+
netsh int ipv4 add excludedportrange protocol=tcp startport=51121 numberofports=1
|
|
309
|
+
==============================================================`;
|
|
238
310
|
} else {
|
|
239
|
-
|
|
311
|
+
errorMsg += `\n\nTry setting a custom port: OAUTH_CALLBACK_PORT=3456`;
|
|
240
312
|
}
|
|
241
|
-
});
|
|
242
313
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
314
|
+
reject(new Error(errorMsg));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
246
317
|
|
|
247
318
|
// Timeout after specified duration
|
|
248
319
|
timeoutId = setTimeout(() => {
|
|
@@ -266,7 +337,10 @@ export function startCallbackServer(expectedState, timeoutMs = 120000) {
|
|
|
266
337
|
}
|
|
267
338
|
};
|
|
268
339
|
|
|
269
|
-
|
|
340
|
+
// Get actual port (useful when fallback is used)
|
|
341
|
+
const getPort = () => actualPort;
|
|
342
|
+
|
|
343
|
+
return { promise, abort, getPort };
|
|
270
344
|
}
|
|
271
345
|
|
|
272
346
|
/**
|
package/src/constants.js
CHANGED
|
@@ -188,13 +188,19 @@ export function isThinkingModel(modelName) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// Google OAuth configuration (from opencode-antigravity-auth)
|
|
191
|
+
// OAuth callback port - configurable via environment variable for Windows compatibility (issue #176)
|
|
192
|
+
// Windows may reserve ports in range 49152-65535 for Hyper-V/WSL2/Docker, causing EACCES errors
|
|
193
|
+
const OAUTH_CALLBACK_PORT = parseInt(process.env.OAUTH_CALLBACK_PORT || '51121', 10);
|
|
194
|
+
const OAUTH_CALLBACK_FALLBACK_PORTS = [51122, 51123, 51124, 51125, 51126];
|
|
195
|
+
|
|
191
196
|
export const OAUTH_CONFIG = {
|
|
192
197
|
clientId: '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com',
|
|
193
198
|
clientSecret: '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',
|
|
194
199
|
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
195
200
|
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
196
201
|
userInfoUrl: 'https://www.googleapis.com/oauth2/v1/userinfo',
|
|
197
|
-
callbackPort:
|
|
202
|
+
callbackPort: OAUTH_CALLBACK_PORT,
|
|
203
|
+
callbackFallbackPorts: OAUTH_CALLBACK_FALLBACK_PORTS,
|
|
198
204
|
scopes: [
|
|
199
205
|
'https://www.googleapis.com/auth/cloud-platform',
|
|
200
206
|
'https://www.googleapis.com/auth/userinfo.email',
|
|
@@ -236,7 +242,7 @@ export const DEFAULT_PRESETS = [
|
|
|
236
242
|
ANTHROPIC_MODEL: 'claude-opus-4-5-thinking',
|
|
237
243
|
ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-5-thinking',
|
|
238
244
|
ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-5-thinking',
|
|
239
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: '
|
|
245
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-sonnet-4-5',
|
|
240
246
|
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-5-thinking',
|
|
241
247
|
ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
|
|
242
248
|
}
|
|
@@ -249,7 +255,7 @@ export const DEFAULT_PRESETS = [
|
|
|
249
255
|
ANTHROPIC_MODEL: 'gemini-3-pro-high[1m]',
|
|
250
256
|
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3-pro-high[1m]',
|
|
251
257
|
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3-flash[1m]',
|
|
252
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-
|
|
258
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-3-flash[1m]',
|
|
253
259
|
CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3-flash[1m]',
|
|
254
260
|
ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
|
|
255
261
|
}
|