antigravity-claude-proxy 3.0.0 → 3.1.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/bin/cli.js +4 -11
- package/package.json +1 -1
- package/public/app.js +7 -4
- package/public/css/src/input.css +379 -216
- package/public/css/style.css +1 -1
- package/public/favicon.svg +4 -8
- package/public/index.html +78 -72
- package/public/js/components/claude-config.js +0 -8
- package/public/js/components/dashboard/charts.js +25 -51
- package/public/js/components/dashboard/stats.js +1 -1
- package/public/js/components/dashboard.js +16 -21
- package/public/js/components/logs-viewer.js +1 -1
- package/public/js/components/models.js +12 -12
- package/public/js/components/server-config.js +1 -9
- package/public/js/data-store.js +2 -2
- package/public/js/store.js +1 -1
- package/public/js/translations/en.js +37 -39
- package/public/js/translations/id.js +36 -37
- package/public/js/translations/pt.js +37 -38
- package/public/js/translations/tr.js +37 -39
- package/public/js/translations/zh.js +0 -2
- package/public/js/utils/error-handler.js +0 -2
- package/public/js/utils/ui-logger.js +4 -3
- package/public/views/accounts.html +34 -38
- package/public/views/dashboard.html +86 -99
- package/public/views/logs.html +4 -4
- package/public/views/models.html +32 -36
- package/public/views/settings.html +50 -50
- package/src/account-manager/index.js +0 -4
- package/src/account-manager/rate-limits.js +0 -5
- package/src/account-manager/strategies/base-strategy.js +4 -4
- package/src/account-manager/strategies/hybrid-strategy.js +3 -3
- package/src/auth/database.js +1 -0
- package/src/auth/oauth.js +43 -41
- package/src/auth/token-extractor.js +1 -1
- package/src/cli/accounts.js +2 -1
- package/src/cloudcode/model-registry.js +0 -3
- package/src/cloudcode/rate-limit-state.js +1 -7
- package/src/config.js +1 -1
- package/src/constants.js +0 -2
- package/src/format/content-converter.js +0 -4
- package/src/format/request-converter.js +1 -6
- package/src/format/response-converter.js +0 -3
- package/src/format/schema-sanitizer.js +3 -22
- package/src/format/thinking-utils.js +1 -13
- package/src/index.js +1 -6
- package/src/modules/usage-stats.js +1 -1
- package/src/server.js +1 -28
- package/src/utils/claude-config.js +2 -6
- package/src/utils/native-module-helper.js +0 -4
- package/src/utils/proxy.js +1 -10
- package/src/utils/version-detector.js +14 -5
- package/src/webui/index.js +1 -37
package/src/auth/oauth.js
CHANGED
|
@@ -178,7 +178,7 @@ export function startCallbackServer(expectedState, timeoutMs = 120000) {
|
|
|
178
178
|
let actualPort = OAUTH_CONFIG.callbackPort;
|
|
179
179
|
const host = process.env.HOST || '0.0.0.0';
|
|
180
180
|
|
|
181
|
-
const promise = new Promise(
|
|
181
|
+
const promise = new Promise((resolve, reject) => {
|
|
182
182
|
// Build list of ports to try: primary + fallbacks
|
|
183
183
|
const portsToTry = [OAUTH_CONFIG.callbackPort, ...(OAUTH_CONFIG.callbackFallbackPorts || [])];
|
|
184
184
|
const errors = [];
|
|
@@ -265,37 +265,38 @@ export function startCallbackServer(expectedState, timeoutMs = 120000) {
|
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
// Try ports with fallback logic (issue #176 - Windows EACCES fix)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
268
|
+
(async () => {
|
|
269
|
+
let boundSuccessfully = false;
|
|
270
|
+
for (const port of portsToTry) {
|
|
271
|
+
try {
|
|
272
|
+
await tryBindPort(server, port, host);
|
|
273
|
+
actualPort = port;
|
|
274
|
+
boundSuccessfully = true;
|
|
275
|
+
|
|
276
|
+
if (port !== OAUTH_CONFIG.callbackPort) {
|
|
277
|
+
logger.warn(`[OAuth] Primary port ${OAUTH_CONFIG.callbackPort} unavailable, using fallback port ${port}`);
|
|
278
|
+
} else {
|
|
279
|
+
logger.info(`[OAuth] Callback server listening on ${host}:${port}`);
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
const errMsg = err.code === 'EACCES'
|
|
284
|
+
? `Permission denied on port ${port}`
|
|
285
|
+
: err.code === 'EADDRINUSE'
|
|
286
|
+
? `Port ${port} already in use`
|
|
287
|
+
: `Failed to bind port ${port}: ${err.message}`;
|
|
288
|
+
errors.push(errMsg);
|
|
289
|
+
logger.warn(`[OAuth] ${errMsg}`);
|
|
279
290
|
}
|
|
280
|
-
break;
|
|
281
|
-
} catch (err) {
|
|
282
|
-
const errMsg = err.code === 'EACCES'
|
|
283
|
-
? `Permission denied on port ${port}`
|
|
284
|
-
: err.code === 'EADDRINUSE'
|
|
285
|
-
? `Port ${port} already in use`
|
|
286
|
-
: `Failed to bind port ${port}: ${err.message}`;
|
|
287
|
-
errors.push(errMsg);
|
|
288
|
-
logger.warn(`[OAuth] ${errMsg}`);
|
|
289
291
|
}
|
|
290
|
-
}
|
|
291
292
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
if (!boundSuccessfully) {
|
|
294
|
+
// All ports failed - provide helpful error message
|
|
295
|
+
const isWindows = process.platform === 'win32';
|
|
296
|
+
let errorMsg = `Failed to start OAuth callback server.\nTried ports: ${portsToTry.join(', ')}\n\nErrors:\n${errors.join('\n')}`;
|
|
296
297
|
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
if (isWindows) {
|
|
299
|
+
errorMsg += `\n
|
|
299
300
|
================== WINDOWS TROUBLESHOOTING ==================
|
|
300
301
|
The default port range may be reserved by Hyper-V/WSL2/Docker.
|
|
301
302
|
|
|
@@ -311,21 +312,22 @@ Option 3: Check reserved port ranges
|
|
|
311
312
|
Option 4: Exclude port from reservation (run as Administrator)
|
|
312
313
|
netsh int ipv4 add excludedportrange protocol=tcp startport=51121 numberofports=1
|
|
313
314
|
==============================================================`;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
reject(new Error(errorMsg));
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
315
|
+
} else {
|
|
316
|
+
errorMsg += `\n\nTry setting a custom port: OAUTH_CALLBACK_PORT=3456`;
|
|
317
|
+
}
|
|
321
318
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (!isAborted) {
|
|
325
|
-
server.close();
|
|
326
|
-
reject(new Error('OAuth callback timeout - no response received'));
|
|
319
|
+
reject(new Error(errorMsg));
|
|
320
|
+
return;
|
|
327
321
|
}
|
|
328
|
-
|
|
322
|
+
|
|
323
|
+
// Timeout after specified duration
|
|
324
|
+
timeoutId = setTimeout(() => {
|
|
325
|
+
if (!isAborted) {
|
|
326
|
+
server.close();
|
|
327
|
+
reject(new Error('OAuth callback timeout - no response received'));
|
|
328
|
+
}
|
|
329
|
+
}, timeoutMs);
|
|
330
|
+
})().catch(reject);
|
|
329
331
|
});
|
|
330
332
|
|
|
331
333
|
// Abort function to clean up server when manual completion happens
|
|
@@ -60,7 +60,7 @@ async function getTokenData() {
|
|
|
60
60
|
return dbData;
|
|
61
61
|
}
|
|
62
62
|
} catch (err) {
|
|
63
|
-
logger.warn(
|
|
63
|
+
logger.warn(`[Token] DB extraction failed (${err.message}), trying HTML page...`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Fallback to HTML page
|
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', () => {
|
|
54
54
|
socket.destroy();
|
|
55
55
|
resolve(false); // Port free
|
|
56
56
|
});
|
|
@@ -447,6 +447,7 @@ async function verifyAccounts() {
|
|
|
447
447
|
console.log(` ✓ ${email} - OK`);
|
|
448
448
|
} catch (error) {
|
|
449
449
|
console.log(` ✗ ${account.email} - ${error.message}`);
|
|
450
|
+
process.exitCode = 1;
|
|
450
451
|
}
|
|
451
452
|
}
|
|
452
453
|
}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Rate Limit State Management
|
|
3
|
-
*
|
|
4
|
-
* Shared utilities for rate limit tracking, backoff calculation,
|
|
5
|
-
* and error classification. Used by both streaming and non-streaming handlers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
1
|
+
// Rate limit tracking, backoff and error classification shared by streaming and non-streaming handlers.
|
|
8
2
|
import {
|
|
9
3
|
RATE_LIMIT_DEDUP_WINDOW_MS,
|
|
10
4
|
RATE_LIMIT_STATE_RESET_MS,
|
package/src/config.js
CHANGED
package/src/constants.js
CHANGED
|
@@ -301,13 +301,11 @@ export const BUILTIN_MODEL_MAPPINGS = {
|
|
|
301
301
|
'claude-sonnet-5': 'claude-sonnet-4-6'
|
|
302
302
|
};
|
|
303
303
|
|
|
304
|
-
// Default test models for each family (used by test suite)
|
|
305
304
|
export const TEST_MODELS = {
|
|
306
305
|
claude: 'claude-sonnet-4-6',
|
|
307
306
|
gemini: 'gemini-3.8-flash-tiered'
|
|
308
307
|
};
|
|
309
308
|
|
|
310
|
-
// Default Claude CLI presets (used by WebUI settings)
|
|
311
309
|
export const DEFAULT_PRESETS = [
|
|
312
310
|
{
|
|
313
311
|
name: 'Claude Thinking',
|
|
@@ -46,7 +46,6 @@ export function convertContentToParts(content, isClaudeModel = false, isGeminiMo
|
|
|
46
46
|
parts.push({ text: block.text });
|
|
47
47
|
}
|
|
48
48
|
} else if (block.type === 'image') {
|
|
49
|
-
// Handle image content
|
|
50
49
|
if (block.source?.type === 'base64') {
|
|
51
50
|
// Base64-encoded image
|
|
52
51
|
parts.push({
|
|
@@ -93,7 +92,6 @@ export function convertContentToParts(content, isClaudeModel = false, isGeminiMo
|
|
|
93
92
|
functionCall.id = block.id;
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
// Build the part with functionCall
|
|
97
95
|
const part = { functionCall };
|
|
98
96
|
|
|
99
97
|
// For Gemini models, include thoughtSignature at the part level
|
|
@@ -133,7 +131,6 @@ export function convertContentToParts(content, isClaudeModel = false, isGeminiMo
|
|
|
133
131
|
}
|
|
134
132
|
}
|
|
135
133
|
|
|
136
|
-
// Extract text content
|
|
137
134
|
const texts = responseContent
|
|
138
135
|
.filter(c => c.type === 'text')
|
|
139
136
|
.map(c => c.text)
|
|
@@ -157,7 +154,6 @@ export function convertContentToParts(content, isClaudeModel = false, isGeminiMo
|
|
|
157
154
|
// This ensures all functionResponse parts are consecutive
|
|
158
155
|
deferredInlineData.push(...imageParts);
|
|
159
156
|
} else if (block.type === 'thinking') {
|
|
160
|
-
// Handle thinking blocks with signature compatibility check
|
|
161
157
|
if (block.signature && block.signature.length >= MIN_SIGNATURE_LENGTH) {
|
|
162
158
|
const signatureFamily = getCachedSignatureFamily(block.signature);
|
|
163
159
|
const targetFamily = isClaudeModel ? 'claude' : isGeminiModel ? 'gemini' : null;
|
|
@@ -51,7 +51,7 @@ export function convertAnthropicToGoogle(anthropicRequest) {
|
|
|
51
51
|
// before any other processing, following the pattern from Antigravity-Manager.
|
|
52
52
|
const messages = cleanCacheControl(anthropicRequest.messages || []);
|
|
53
53
|
|
|
54
|
-
const { system, max_tokens, temperature, top_p, top_k, stop_sequences, tools,
|
|
54
|
+
const { system, max_tokens, temperature, top_p, top_k, stop_sequences, tools, thinking } = anthropicRequest;
|
|
55
55
|
const modelName = anthropicRequest.model || '';
|
|
56
56
|
const modelFamily = getModelFamily(modelName);
|
|
57
57
|
const isClaudeModel = modelFamily === 'claude';
|
|
@@ -63,7 +63,6 @@ export function convertAnthropicToGoogle(anthropicRequest) {
|
|
|
63
63
|
generationConfig: {}
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
// Handle system instruction
|
|
67
66
|
if (system) {
|
|
68
67
|
let systemParts = [];
|
|
69
68
|
if (typeof system === 'string') {
|
|
@@ -126,7 +125,6 @@ export function convertAnthropicToGoogle(anthropicRequest) {
|
|
|
126
125
|
if ((msg.role === 'assistant' || msg.role === 'model') && Array.isArray(msgContent)) {
|
|
127
126
|
// First, try to restore signatures for unsigned thinking blocks from cache
|
|
128
127
|
msgContent = restoreThinkingSignatures(msgContent);
|
|
129
|
-
// Remove trailing unsigned thinking blocks
|
|
130
128
|
msgContent = removeTrailingThinkingBlocks(msgContent);
|
|
131
129
|
// Reorder: thinking first, then text, then tool_use
|
|
132
130
|
msgContent = reorderAssistantContent(msgContent);
|
|
@@ -228,13 +226,10 @@ export function convertAnthropicToGoogle(anthropicRequest) {
|
|
|
228
226
|
// Convert tools to Google format
|
|
229
227
|
if (tools && tools.length > 0) {
|
|
230
228
|
const functionDeclarations = tools.map((tool, idx) => {
|
|
231
|
-
// Extract name from various possible locations
|
|
232
229
|
const name = tool.name || tool.function?.name || tool.custom?.name || `tool-${idx}`;
|
|
233
230
|
|
|
234
|
-
// Extract description from various possible locations
|
|
235
231
|
const description = tool.description || tool.function?.description || tool.custom?.description || '';
|
|
236
232
|
|
|
237
|
-
// Extract schema from various possible locations
|
|
238
233
|
const schema = tool.input_schema
|
|
239
234
|
|| tool.function?.input_schema
|
|
240
235
|
|| tool.function?.parameters
|
|
@@ -15,7 +15,6 @@ import { cacheSignature, cacheThinkingSignature } from './signature-cache.js';
|
|
|
15
15
|
* @returns {Object} Anthropic format response
|
|
16
16
|
*/
|
|
17
17
|
export function convertGoogleToAnthropic(googleResponse, model) {
|
|
18
|
-
// Handle the response wrapper
|
|
19
18
|
const response = googleResponse.response || googleResponse;
|
|
20
19
|
|
|
21
20
|
const candidates = response.candidates || [];
|
|
@@ -29,7 +28,6 @@ export function convertGoogleToAnthropic(googleResponse, model) {
|
|
|
29
28
|
|
|
30
29
|
for (const part of parts) {
|
|
31
30
|
if (part.text !== undefined) {
|
|
32
|
-
// Handle thinking blocks
|
|
33
31
|
if (part.thought === true) {
|
|
34
32
|
const signature = part.thoughtSignature || '';
|
|
35
33
|
|
|
@@ -72,7 +70,6 @@ export function convertGoogleToAnthropic(googleResponse, model) {
|
|
|
72
70
|
anthropicContent.push(toolUseBlock);
|
|
73
71
|
hasToolCalls = true;
|
|
74
72
|
} else if (part.inlineData) {
|
|
75
|
-
// Handle image content from Google format
|
|
76
73
|
anthropicContent.push({
|
|
77
74
|
type: 'image',
|
|
78
75
|
source: {
|
|
@@ -163,7 +163,7 @@ function mergeAllOf(schema) {
|
|
|
163
163
|
|
|
164
164
|
// Merge properties (allOf properties override parent for same keys)
|
|
165
165
|
if (Object.keys(mergedProperties).length > 0) {
|
|
166
|
-
result.properties = { ...mergedProperties, ...
|
|
166
|
+
result.properties = { ...mergedProperties, ...result.properties };
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
// Merge required
|
|
@@ -289,10 +289,6 @@ function flattenAnyOfOneOf(schema) {
|
|
|
289
289
|
return result;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
// ============================================================================
|
|
293
|
-
// Enhanced Schema Hints (for preserving semantic information)
|
|
294
|
-
// ============================================================================
|
|
295
|
-
|
|
296
292
|
/**
|
|
297
293
|
* Add hints for enum values (if ≤10 values).
|
|
298
294
|
* This preserves enum information in the description since Gemini
|
|
@@ -611,7 +607,6 @@ export function cleanSchema(schema) {
|
|
|
611
607
|
if (!schema || typeof schema !== 'object') return schema;
|
|
612
608
|
if (Array.isArray(schema)) return schema.map(cleanSchema);
|
|
613
609
|
|
|
614
|
-
// Phase 1: Convert $refs to hints
|
|
615
610
|
let result = convertRefsToHints(schema);
|
|
616
611
|
|
|
617
612
|
// Phase 1b: Add enum hints (preserves enum info in description)
|
|
@@ -632,7 +627,6 @@ export function cleanSchema(schema) {
|
|
|
632
627
|
// Phase 2c: Flatten type arrays and update required for nullable
|
|
633
628
|
result = flattenTypeArrays(result);
|
|
634
629
|
|
|
635
|
-
// Phase 3: Remove unsupported keywords
|
|
636
630
|
const unsupported = [
|
|
637
631
|
'additionalProperties', 'default', '$schema', '$defs',
|
|
638
632
|
'definitions', '$ref', '$id', '$comment', 'title',
|
|
@@ -652,7 +646,6 @@ export function cleanSchema(schema) {
|
|
|
652
646
|
}
|
|
653
647
|
}
|
|
654
648
|
|
|
655
|
-
// Phase 4: Final cleanup - recursively clean nested schemas and validate required
|
|
656
649
|
if (result.properties && typeof result.properties === 'object') {
|
|
657
650
|
const newProps = {};
|
|
658
651
|
for (const [key, value] of Object.entries(result.properties)) {
|
|
@@ -678,21 +671,11 @@ export function cleanSchema(schema) {
|
|
|
678
671
|
}
|
|
679
672
|
}
|
|
680
673
|
|
|
681
|
-
// Phase 5: Convert type to Google's uppercase format (STRING, OBJECT, ARRAY, etc.)
|
|
682
|
-
// Only convert at current level - nested types already converted by recursive cleanSchema calls
|
|
683
674
|
if (result.type && typeof result.type === 'string') {
|
|
684
675
|
result.type = toGoogleType(result.type);
|
|
685
676
|
}
|
|
686
677
|
|
|
687
|
-
//
|
|
688
|
-
// declare a single object `items` schema. Plain JSON Schema allows this
|
|
689
|
-
// to be omitted (`{ type: 'array' }` = "array of anything"), boolean
|
|
690
|
-
// (`items: true` = "any"), or tuple-form (`items: [...]`) - none of
|
|
691
|
-
// which the Cloud Code API accepts; it rejects the first two with
|
|
692
|
-
// "items: missing field". This runs after the items/properties
|
|
693
|
-
// recursion above (and after Phase 5's type conversion), so it applies
|
|
694
|
-
// at every nesting depth - fixing issue #368 (nested array schemas like
|
|
695
|
-
// `where.items.items` losing their innermost `items`).
|
|
678
|
+
// Cloud Code rejects ARRAY schemas without a single object `items` ("items: missing field"), issue #368.
|
|
696
679
|
if (result.type === 'ARRAY') {
|
|
697
680
|
if (!result.items || typeof result.items !== 'object') {
|
|
698
681
|
// Missing entirely, or a boolean schema like `items: true`.
|
|
@@ -713,9 +696,7 @@ export function cleanSchema(schema) {
|
|
|
713
696
|
}
|
|
714
697
|
}
|
|
715
698
|
|
|
716
|
-
//
|
|
717
|
-
// These can survive on the current node after anyOf/oneOf flattening
|
|
718
|
-
// picked a non-object option while the parent still carried them.
|
|
699
|
+
// Google rejects properties/required on non-OBJECT nodes, which anyOf/oneOf flattening can leave behind.
|
|
719
700
|
if (result.type !== 'OBJECT') {
|
|
720
701
|
delete result.properties;
|
|
721
702
|
delete result.required;
|
|
@@ -7,10 +7,6 @@ import { MIN_SIGNATURE_LENGTH } from '../constants.js';
|
|
|
7
7
|
import { getCachedSignature, getCachedSignatureFamily } from './signature-cache.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
9
|
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Gemini Thinking Budget Limits (Issue #289)
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
10
|
// Max thinking budget per Gemini model version
|
|
15
11
|
// Gemini 2.5 Flash: max 24,576 (API error: "supported values are integers from 1 to 24576")
|
|
16
12
|
const GEMINI_THINKING_BUDGET_LIMITS = {
|
|
@@ -57,10 +53,6 @@ export function clampGeminiThinkingBudget(modelName, budget) {
|
|
|
57
53
|
return requestedBudget;
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
// ============================================================================
|
|
61
|
-
// Cache Control Cleaning (Issue #189)
|
|
62
|
-
// ============================================================================
|
|
63
|
-
|
|
64
56
|
/**
|
|
65
57
|
* Remove cache_control fields from all content blocks in messages.
|
|
66
58
|
* This is a critical fix for Issue #189 where Claude Code CLI sends cache_control
|
|
@@ -94,7 +86,7 @@ export function cleanCacheControl(messages) {
|
|
|
94
86
|
if (block.cache_control === undefined) return block;
|
|
95
87
|
|
|
96
88
|
// Create a shallow copy without cache_control
|
|
97
|
-
const { cache_control, ...cleanBlock } = block;
|
|
89
|
+
const { cache_control: _cacheControl, ...cleanBlock } = block;
|
|
98
90
|
removedCount++;
|
|
99
91
|
|
|
100
92
|
return cleanBlock;
|
|
@@ -431,10 +423,6 @@ export function reorderAssistantContent(content) {
|
|
|
431
423
|
return reordered;
|
|
432
424
|
}
|
|
433
425
|
|
|
434
|
-
// ============================================================================
|
|
435
|
-
// Thinking Recovery Functions
|
|
436
|
-
// ============================================================================
|
|
437
|
-
|
|
438
426
|
/**
|
|
439
427
|
* Check if a message has any VALID (signed) thinking blocks.
|
|
440
428
|
* Only counts thinking blocks that have valid signatures, not unsigned ones
|
package/src/index.js
CHANGED
|
@@ -10,14 +10,13 @@ import app, { accountManager } from './server.js';
|
|
|
10
10
|
import { DEFAULT_PORT } from './constants.js';
|
|
11
11
|
import { logger } from './utils/logger.js';
|
|
12
12
|
import { config } from './config.js';
|
|
13
|
-
import {
|
|
13
|
+
import { STRATEGY_NAMES } from './account-manager/strategies/index.js';
|
|
14
14
|
import { getPackageVersion } from './utils/helpers.js';
|
|
15
15
|
import path from 'path';
|
|
16
16
|
import os from 'os';
|
|
17
17
|
|
|
18
18
|
const packageVersion = getPackageVersion();
|
|
19
19
|
|
|
20
|
-
// Parse command line arguments
|
|
21
20
|
const args = process.argv.slice(2);
|
|
22
21
|
const isDebug = args.includes('--debug') || args.includes('--dev-mode') || process.env.DEBUG === 'true' || process.env.DEV_MODE === 'true';
|
|
23
22
|
const isFallbackEnabled = args.includes('--fallback') || process.env.FALLBACK === 'true';
|
|
@@ -31,13 +30,11 @@ for (let i = 0; i < args.length; i++) {
|
|
|
31
30
|
strategyOverride = args[i + 1];
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
// Validate strategy
|
|
35
33
|
if (strategyOverride && !STRATEGY_NAMES.includes(strategyOverride.toLowerCase())) {
|
|
36
34
|
logger.warn(`[Startup] Invalid strategy "${strategyOverride}". Valid options: ${STRATEGY_NAMES.join(', ')}. Using default.`);
|
|
37
35
|
strategyOverride = null;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
// Initialize logger and devMode
|
|
41
38
|
logger.setDebug(isDebug);
|
|
42
39
|
|
|
43
40
|
if (isDebug) {
|
|
@@ -65,7 +62,6 @@ const HOME_DIR = os.homedir();
|
|
|
65
62
|
const CONFIG_DIR = path.join(HOME_DIR, '.antigravity-claude-proxy');
|
|
66
63
|
|
|
67
64
|
const server = app.listen(PORT, HOST, () => {
|
|
68
|
-
// Get actual bound address
|
|
69
65
|
const address = server.address();
|
|
70
66
|
const boundHost = typeof address === 'string' ? address : address.address;
|
|
71
67
|
const boundPort = typeof address === 'string' ? null : address.port;
|
|
@@ -78,7 +74,6 @@ const server = app.listen(PORT, HOST, () => {
|
|
|
78
74
|
const align = (text) => text + ' '.repeat(Math.max(0, 60 - text.length));
|
|
79
75
|
const align4 = (text) => text + ' '.repeat(Math.max(0, 58 - text.length));
|
|
80
76
|
|
|
81
|
-
// Build Control section dynamically
|
|
82
77
|
const strategyOptions = `(${STRATEGY_NAMES.join('/')})`;
|
|
83
78
|
const strategyLine2 = ' ' + strategyOptions;
|
|
84
79
|
let controlSection = '║ Control: ║\n';
|
package/src/server.js
CHANGED
|
@@ -46,7 +46,6 @@ export const accountManager = new AccountManager();
|
|
|
46
46
|
|
|
47
47
|
// Track initialization status
|
|
48
48
|
let isInitialized = false;
|
|
49
|
-
let initError = null;
|
|
50
49
|
let initPromise = null;
|
|
51
50
|
|
|
52
51
|
/**
|
|
@@ -65,7 +64,6 @@ async function ensureInitialized() {
|
|
|
65
64
|
const status = accountManager.getStatus();
|
|
66
65
|
logger.success(`[Server] Account pool initialized: ${status.summary}`);
|
|
67
66
|
} catch (error) {
|
|
68
|
-
initError = error;
|
|
69
67
|
initPromise = null; // Allow retry on failure
|
|
70
68
|
logger.error('[Server] Failed to initialize account manager:', error.message);
|
|
71
69
|
throw error;
|
|
@@ -116,7 +114,6 @@ app.use('/v1', (req, res, next) => {
|
|
|
116
114
|
next();
|
|
117
115
|
});
|
|
118
116
|
|
|
119
|
-
// Setup usage statistics middleware
|
|
120
117
|
usageStats.setupMiddleware(app);
|
|
121
118
|
|
|
122
119
|
/**
|
|
@@ -125,11 +122,9 @@ usageStats.setupMiddleware(app);
|
|
|
125
122
|
* Using app.use instead of app.post for earlier middleware interception
|
|
126
123
|
*/
|
|
127
124
|
app.use((req, res, next) => {
|
|
128
|
-
// Handle Claude Code event logging requests silently
|
|
129
125
|
if (req.method === 'POST' && req.path === '/api/event_logging/batch') {
|
|
130
126
|
return res.status(200).json({ status: 'ok' });
|
|
131
127
|
}
|
|
132
|
-
// Handle Claude Code root POST requests silently
|
|
133
128
|
if (req.method === 'POST' && req.path === '/') {
|
|
134
129
|
return res.status(200).json({ status: 'ok' });
|
|
135
130
|
}
|
|
@@ -178,7 +173,6 @@ function parseError(error) {
|
|
|
178
173
|
} else if (error.message.includes('PERMISSION_DENIED')) {
|
|
179
174
|
errorType = 'permission_error';
|
|
180
175
|
statusCode = 403;
|
|
181
|
-
errorMessage = errorMessage;
|
|
182
176
|
}
|
|
183
177
|
|
|
184
178
|
return { errorType, statusCode, errorMessage };
|
|
@@ -188,7 +182,6 @@ function parseError(error) {
|
|
|
188
182
|
app.use((req, res, next) => {
|
|
189
183
|
const start = Date.now();
|
|
190
184
|
|
|
191
|
-
// Log response on finish
|
|
192
185
|
res.on('finish', () => {
|
|
193
186
|
const duration = Date.now() - start;
|
|
194
187
|
const status = res.statusCode;
|
|
@@ -241,14 +234,12 @@ app.get('/health', async (req, res) => {
|
|
|
241
234
|
await ensureInitialized();
|
|
242
235
|
const start = Date.now();
|
|
243
236
|
|
|
244
|
-
// Get high-level status first
|
|
245
237
|
const status = accountManager.getStatus();
|
|
246
238
|
const allAccounts = accountManager.getAllAccounts();
|
|
247
239
|
|
|
248
240
|
// Fetch quotas for each account in parallel to get detailed model info
|
|
249
241
|
const accountDetails = await Promise.allSettled(
|
|
250
242
|
allAccounts.map(async (account) => {
|
|
251
|
-
// Check model-specific rate limits
|
|
252
243
|
const activeModelLimits = Object.entries(account.modelRateLimits || {})
|
|
253
244
|
.filter(([_, limit]) => limit.isRateLimited && limit.resetTime > Date.now());
|
|
254
245
|
const isRateLimited = activeModelLimits.length > 0;
|
|
@@ -306,7 +297,6 @@ app.get('/health', async (req, res) => {
|
|
|
306
297
|
})
|
|
307
298
|
);
|
|
308
299
|
|
|
309
|
-
// Process results
|
|
310
300
|
const detailedAccounts = accountDetails.map((result, index) => {
|
|
311
301
|
if (result.status === 'fulfilled') {
|
|
312
302
|
return result.value;
|
|
@@ -357,7 +347,6 @@ app.get('/account-limits', async (req, res) => {
|
|
|
357
347
|
const format = req.query.format || 'json';
|
|
358
348
|
const includeHistory = req.query.includeHistory === 'true';
|
|
359
349
|
|
|
360
|
-
// Fetch quotas for each account in parallel
|
|
361
350
|
const results = await Promise.allSettled(
|
|
362
351
|
allAccounts.map(async (account) => {
|
|
363
352
|
// Skip invalid accounts
|
|
@@ -373,13 +362,11 @@ app.get('/account-limits', async (req, res) => {
|
|
|
373
362
|
try {
|
|
374
363
|
const token = await accountManager.getTokenForAccount(account);
|
|
375
364
|
|
|
376
|
-
// Fetch subscription tier first to get project ID
|
|
377
365
|
const subscription = await getSubscriptionTier(token);
|
|
378
366
|
|
|
379
367
|
// Then fetch quotas with project ID for accurate quota info
|
|
380
368
|
const quotas = await getModelQuotas(token, subscription.projectId);
|
|
381
369
|
|
|
382
|
-
// Update account object with fresh data
|
|
383
370
|
account.subscription = {
|
|
384
371
|
tier: subscription.tier,
|
|
385
372
|
projectId: subscription.projectId,
|
|
@@ -424,7 +411,6 @@ app.get('/account-limits', async (req, res) => {
|
|
|
424
411
|
})
|
|
425
412
|
);
|
|
426
413
|
|
|
427
|
-
// Process results
|
|
428
414
|
const accountLimits = results.map((result, index) => {
|
|
429
415
|
if (result.status === 'fulfilled') {
|
|
430
416
|
return result.value;
|
|
@@ -448,16 +434,13 @@ app.get('/account-limits', async (req, res) => {
|
|
|
448
434
|
|
|
449
435
|
const sortedModels = Array.from(allModelIds).sort();
|
|
450
436
|
|
|
451
|
-
// Return ASCII table format
|
|
452
437
|
if (format === 'table') {
|
|
453
438
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
454
439
|
|
|
455
|
-
// Build table
|
|
456
440
|
const lines = [];
|
|
457
441
|
const timestamp = new Date().toLocaleString();
|
|
458
442
|
lines.push(`Account Limits (${timestamp})`);
|
|
459
443
|
|
|
460
|
-
// Get account status info
|
|
461
444
|
const status = accountManager.getStatus();
|
|
462
445
|
lines.push(`Accounts: ${status.total} total, ${status.available} available, ${status.rateLimited} rate-limited, ${status.invalid} invalid`);
|
|
463
446
|
lines.push('');
|
|
@@ -476,7 +459,6 @@ app.get('/account-limits', async (req, res) => {
|
|
|
476
459
|
const shortEmail = acc.email.split('@')[0].slice(0, 22);
|
|
477
460
|
const lastUsed = acc.lastUsed ? new Date(acc.lastUsed).toLocaleString() : 'never';
|
|
478
461
|
|
|
479
|
-
// Get status and error from accountLimits
|
|
480
462
|
const accLimit = accountLimits.find(a => a.email === acc.email);
|
|
481
463
|
let accStatus;
|
|
482
464
|
if (acc.isInvalid) {
|
|
@@ -498,7 +480,6 @@ app.get('/account-limits', async (req, res) => {
|
|
|
498
480
|
}
|
|
499
481
|
}
|
|
500
482
|
|
|
501
|
-
// Get reset time from quota API
|
|
502
483
|
const claudeModel = sortedModels.find(m => m.includes('claude'));
|
|
503
484
|
const quota = claudeModel && accLimit?.models?.[claudeModel];
|
|
504
485
|
const resetTime = quota?.resetTime
|
|
@@ -564,13 +545,11 @@ app.get('/account-limits', async (req, res) => {
|
|
|
564
545
|
return res.send(lines.join('\n'));
|
|
565
546
|
}
|
|
566
547
|
|
|
567
|
-
// Get account metadata from AccountManager
|
|
568
548
|
const accountStatus = accountManager.getStatus();
|
|
569
549
|
const accountMetadataMap = new Map(
|
|
570
550
|
accountStatus.accounts.map(a => [a.email, a])
|
|
571
551
|
);
|
|
572
552
|
|
|
573
|
-
// Build response data
|
|
574
553
|
const responseData = {
|
|
575
554
|
timestamp: new Date().toLocaleString(),
|
|
576
555
|
totalAccounts: allAccounts.length,
|
|
@@ -705,7 +684,6 @@ app.post('/v1/messages/count_tokens', (req, res) => {
|
|
|
705
684
|
* Main messages endpoint - Anthropic Messages API compatible
|
|
706
685
|
*/
|
|
707
686
|
|
|
708
|
-
|
|
709
687
|
/**
|
|
710
688
|
* Anthropic-compatible Messages API
|
|
711
689
|
* POST /v1/messages
|
|
@@ -744,7 +722,6 @@ app.post('/v1/messages', async (req, res) => {
|
|
|
744
722
|
|
|
745
723
|
const modelId = requestedModel;
|
|
746
724
|
|
|
747
|
-
// Validate model ID before processing
|
|
748
725
|
const { account: validationAccount } = accountManager.selectAccount();
|
|
749
726
|
if (validationAccount) {
|
|
750
727
|
const token = await accountManager.getTokenForAccount(validationAccount);
|
|
@@ -763,7 +740,6 @@ app.post('/v1/messages', async (req, res) => {
|
|
|
763
740
|
accountManager.resetAllRateLimits();
|
|
764
741
|
}
|
|
765
742
|
|
|
766
|
-
// Validate required fields
|
|
767
743
|
if (!messages || !Array.isArray(messages)) {
|
|
768
744
|
return res.status(400).json({
|
|
769
745
|
type: 'error',
|
|
@@ -779,7 +755,6 @@ app.post('/v1/messages', async (req, res) => {
|
|
|
779
755
|
return res.json({});
|
|
780
756
|
}
|
|
781
757
|
|
|
782
|
-
// Build the request object
|
|
783
758
|
const request = {
|
|
784
759
|
model: modelId,
|
|
785
760
|
messages,
|
|
@@ -813,7 +788,6 @@ app.post('/v1/messages', async (req, res) => {
|
|
|
813
788
|
// to ensure we don't send a 200 OK if the upstream fails immediately (e.g. 429/503).
|
|
814
789
|
|
|
815
790
|
try {
|
|
816
|
-
// Initialize the generator
|
|
817
791
|
const generator = sendMessageStream(request, accountManager, FALLBACK_ENABLED);
|
|
818
792
|
|
|
819
793
|
// BUFFERING STRATEGY:
|
|
@@ -872,7 +846,6 @@ app.post('/v1/messages', async (req, res) => {
|
|
|
872
846
|
}
|
|
873
847
|
|
|
874
848
|
} else {
|
|
875
|
-
// Handle non-streaming response
|
|
876
849
|
const response = await sendMessage(request, accountManager, FALLBACK_ENABLED);
|
|
877
850
|
usageStats.track(response?.model || modelId);
|
|
878
851
|
res.json(response);
|
|
@@ -892,6 +865,7 @@ app.post('/v1/messages', async (req, res) => {
|
|
|
892
865
|
await forceRefresh();
|
|
893
866
|
errorMessage = 'Token was expired and has been refreshed. Please retry your request.';
|
|
894
867
|
} catch (refreshError) {
|
|
868
|
+
logger.warn(`[API] Token refresh failed: ${refreshError.message}`);
|
|
895
869
|
errorMessage = 'Could not refresh token. Make sure Antigravity is running.';
|
|
896
870
|
}
|
|
897
871
|
}
|
|
@@ -924,7 +898,6 @@ app.post('/v1/messages', async (req, res) => {
|
|
|
924
898
|
usageStats.setupRoutes(app);
|
|
925
899
|
|
|
926
900
|
app.use((req, res) => {
|
|
927
|
-
// Log 404s
|
|
928
901
|
if (logger.isDebugEnabled) {
|
|
929
902
|
logger.debug(`[API] 404 Not Found: ${req.method} ${req.originalUrl}`);
|
|
930
903
|
}
|