antigravity-claude-proxy 2.9.3 → 3.0.1

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +4 -11
  3. package/package.json +5 -5
  4. package/public/app.js +7 -4
  5. package/public/css/src/input.css +0 -7
  6. package/public/css/style.css +1 -1
  7. package/public/js/components/claude-config.js +0 -8
  8. package/public/js/components/dashboard/charts.js +8 -8
  9. package/public/js/components/dashboard/stats.js +1 -1
  10. package/public/js/components/logs-viewer.js +1 -1
  11. package/public/js/components/models.js +2 -3
  12. package/public/js/components/server-config.js +1 -9
  13. package/public/js/data-store.js +2 -2
  14. package/public/js/store.js +1 -1
  15. package/public/js/translations/en.js +0 -2
  16. package/public/js/translations/id.js +0 -1
  17. package/public/js/translations/pt.js +0 -1
  18. package/public/js/translations/tr.js +0 -2
  19. package/public/js/translations/zh.js +0 -2
  20. package/public/js/utils/error-handler.js +0 -2
  21. package/public/js/utils/ui-logger.js +4 -3
  22. package/src/account-manager/index.js +0 -4
  23. package/src/account-manager/rate-limits.js +0 -5
  24. package/src/account-manager/strategies/base-strategy.js +4 -4
  25. package/src/account-manager/strategies/hybrid-strategy.js +3 -3
  26. package/src/auth/database.js +1 -0
  27. package/src/auth/oauth.js +43 -41
  28. package/src/auth/token-extractor.js +1 -1
  29. package/src/cli/accounts.js +2 -1
  30. package/src/cloudcode/model-registry.js +0 -3
  31. package/src/cloudcode/rate-limit-state.js +1 -7
  32. package/src/config.js +1 -1
  33. package/src/constants.js +0 -2
  34. package/src/format/content-converter.js +0 -4
  35. package/src/format/request-converter.js +1 -6
  36. package/src/format/response-converter.js +0 -3
  37. package/src/format/schema-sanitizer.js +3 -22
  38. package/src/format/thinking-utils.js +1 -13
  39. package/src/index.js +1 -6
  40. package/src/modules/usage-stats.js +1 -1
  41. package/src/server.js +8 -29
  42. package/src/utils/claude-config.js +2 -6
  43. package/src/utils/native-module-helper.js +0 -4
  44. package/src/utils/proxy.js +1 -10
  45. package/src/utils/version-detector.js +14 -5
  46. package/src/webui/index.js +1 -37
@@ -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, tool_choice, thinking } = anthropicRequest;
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, ...(result.properties || {}) };
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
- // Phase 6: Google's protobuf-based schema requires every ARRAY type to
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
- // Phase 7: Google rejects `properties`/`required` on a non-OBJECT schema.
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 { getStrategyLabel, STRATEGY_NAMES, DEFAULT_STRATEGY } from './account-manager/strategies/index.js';
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';
@@ -179,7 +179,7 @@ function track(modelId) {
179
179
  * Setup Express Middleware
180
180
  * @param {import('express').Application} app
181
181
  */
182
- function setupMiddleware(app) {
182
+ function setupMiddleware(_app) {
183
183
  load();
184
184
 
185
185
  // Auto-save every minute
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;
@@ -78,6 +76,12 @@ async function ensureInitialized() {
78
76
  // Middleware
79
77
  app.use(cors());
80
78
  app.use(express.json({ limit: REQUEST_BODY_LIMIT }));
79
+ // Express 5 leaves req.body undefined when no body was parsed (Express 4 set {}).
80
+ // Keep the old behavior so handlers can destructure req.body safely.
81
+ app.use((req, res, next) => {
82
+ if (req.body === undefined) req.body = {};
83
+ next();
84
+ });
81
85
 
82
86
  // API Key authentication middleware for /v1/* endpoints
83
87
  app.use('/v1', (req, res, next) => {
@@ -110,7 +114,6 @@ app.use('/v1', (req, res, next) => {
110
114
  next();
111
115
  });
112
116
 
113
- // Setup usage statistics middleware
114
117
  usageStats.setupMiddleware(app);
115
118
 
116
119
  /**
@@ -119,11 +122,9 @@ usageStats.setupMiddleware(app);
119
122
  * Using app.use instead of app.post for earlier middleware interception
120
123
  */
121
124
  app.use((req, res, next) => {
122
- // Handle Claude Code event logging requests silently
123
125
  if (req.method === 'POST' && req.path === '/api/event_logging/batch') {
124
126
  return res.status(200).json({ status: 'ok' });
125
127
  }
126
- // Handle Claude Code root POST requests silently
127
128
  if (req.method === 'POST' && req.path === '/') {
128
129
  return res.status(200).json({ status: 'ok' });
129
130
  }
@@ -172,7 +173,6 @@ function parseError(error) {
172
173
  } else if (error.message.includes('PERMISSION_DENIED')) {
173
174
  errorType = 'permission_error';
174
175
  statusCode = 403;
175
- errorMessage = errorMessage;
176
176
  }
177
177
 
178
178
  return { errorType, statusCode, errorMessage };
@@ -182,7 +182,6 @@ function parseError(error) {
182
182
  app.use((req, res, next) => {
183
183
  const start = Date.now();
184
184
 
185
- // Log response on finish
186
185
  res.on('finish', () => {
187
186
  const duration = Date.now() - start;
188
187
  const status = res.statusCode;
@@ -235,14 +234,12 @@ app.get('/health', async (req, res) => {
235
234
  await ensureInitialized();
236
235
  const start = Date.now();
237
236
 
238
- // Get high-level status first
239
237
  const status = accountManager.getStatus();
240
238
  const allAccounts = accountManager.getAllAccounts();
241
239
 
242
240
  // Fetch quotas for each account in parallel to get detailed model info
243
241
  const accountDetails = await Promise.allSettled(
244
242
  allAccounts.map(async (account) => {
245
- // Check model-specific rate limits
246
243
  const activeModelLimits = Object.entries(account.modelRateLimits || {})
247
244
  .filter(([_, limit]) => limit.isRateLimited && limit.resetTime > Date.now());
248
245
  const isRateLimited = activeModelLimits.length > 0;
@@ -300,7 +297,6 @@ app.get('/health', async (req, res) => {
300
297
  })
301
298
  );
302
299
 
303
- // Process results
304
300
  const detailedAccounts = accountDetails.map((result, index) => {
305
301
  if (result.status === 'fulfilled') {
306
302
  return result.value;
@@ -351,7 +347,6 @@ app.get('/account-limits', async (req, res) => {
351
347
  const format = req.query.format || 'json';
352
348
  const includeHistory = req.query.includeHistory === 'true';
353
349
 
354
- // Fetch quotas for each account in parallel
355
350
  const results = await Promise.allSettled(
356
351
  allAccounts.map(async (account) => {
357
352
  // Skip invalid accounts
@@ -367,13 +362,11 @@ app.get('/account-limits', async (req, res) => {
367
362
  try {
368
363
  const token = await accountManager.getTokenForAccount(account);
369
364
 
370
- // Fetch subscription tier first to get project ID
371
365
  const subscription = await getSubscriptionTier(token);
372
366
 
373
367
  // Then fetch quotas with project ID for accurate quota info
374
368
  const quotas = await getModelQuotas(token, subscription.projectId);
375
369
 
376
- // Update account object with fresh data
377
370
  account.subscription = {
378
371
  tier: subscription.tier,
379
372
  projectId: subscription.projectId,
@@ -418,7 +411,6 @@ app.get('/account-limits', async (req, res) => {
418
411
  })
419
412
  );
420
413
 
421
- // Process results
422
414
  const accountLimits = results.map((result, index) => {
423
415
  if (result.status === 'fulfilled') {
424
416
  return result.value;
@@ -442,16 +434,13 @@ app.get('/account-limits', async (req, res) => {
442
434
 
443
435
  const sortedModels = Array.from(allModelIds).sort();
444
436
 
445
- // Return ASCII table format
446
437
  if (format === 'table') {
447
438
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
448
439
 
449
- // Build table
450
440
  const lines = [];
451
441
  const timestamp = new Date().toLocaleString();
452
442
  lines.push(`Account Limits (${timestamp})`);
453
443
 
454
- // Get account status info
455
444
  const status = accountManager.getStatus();
456
445
  lines.push(`Accounts: ${status.total} total, ${status.available} available, ${status.rateLimited} rate-limited, ${status.invalid} invalid`);
457
446
  lines.push('');
@@ -470,7 +459,6 @@ app.get('/account-limits', async (req, res) => {
470
459
  const shortEmail = acc.email.split('@')[0].slice(0, 22);
471
460
  const lastUsed = acc.lastUsed ? new Date(acc.lastUsed).toLocaleString() : 'never';
472
461
 
473
- // Get status and error from accountLimits
474
462
  const accLimit = accountLimits.find(a => a.email === acc.email);
475
463
  let accStatus;
476
464
  if (acc.isInvalid) {
@@ -492,7 +480,6 @@ app.get('/account-limits', async (req, res) => {
492
480
  }
493
481
  }
494
482
 
495
- // Get reset time from quota API
496
483
  const claudeModel = sortedModels.find(m => m.includes('claude'));
497
484
  const quota = claudeModel && accLimit?.models?.[claudeModel];
498
485
  const resetTime = quota?.resetTime
@@ -558,13 +545,11 @@ app.get('/account-limits', async (req, res) => {
558
545
  return res.send(lines.join('\n'));
559
546
  }
560
547
 
561
- // Get account metadata from AccountManager
562
548
  const accountStatus = accountManager.getStatus();
563
549
  const accountMetadataMap = new Map(
564
550
  accountStatus.accounts.map(a => [a.email, a])
565
551
  );
566
552
 
567
- // Build response data
568
553
  const responseData = {
569
554
  timestamp: new Date().toLocaleString(),
570
555
  totalAccounts: allAccounts.length,
@@ -699,7 +684,6 @@ app.post('/v1/messages/count_tokens', (req, res) => {
699
684
  * Main messages endpoint - Anthropic Messages API compatible
700
685
  */
701
686
 
702
-
703
687
  /**
704
688
  * Anthropic-compatible Messages API
705
689
  * POST /v1/messages
@@ -738,7 +722,6 @@ app.post('/v1/messages', async (req, res) => {
738
722
 
739
723
  const modelId = requestedModel;
740
724
 
741
- // Validate model ID before processing
742
725
  const { account: validationAccount } = accountManager.selectAccount();
743
726
  if (validationAccount) {
744
727
  const token = await accountManager.getTokenForAccount(validationAccount);
@@ -757,7 +740,6 @@ app.post('/v1/messages', async (req, res) => {
757
740
  accountManager.resetAllRateLimits();
758
741
  }
759
742
 
760
- // Validate required fields
761
743
  if (!messages || !Array.isArray(messages)) {
762
744
  return res.status(400).json({
763
745
  type: 'error',
@@ -773,7 +755,6 @@ app.post('/v1/messages', async (req, res) => {
773
755
  return res.json({});
774
756
  }
775
757
 
776
- // Build the request object
777
758
  const request = {
778
759
  model: modelId,
779
760
  messages,
@@ -807,7 +788,6 @@ app.post('/v1/messages', async (req, res) => {
807
788
  // to ensure we don't send a 200 OK if the upstream fails immediately (e.g. 429/503).
808
789
 
809
790
  try {
810
- // Initialize the generator
811
791
  const generator = sendMessageStream(request, accountManager, FALLBACK_ENABLED);
812
792
 
813
793
  // BUFFERING STRATEGY:
@@ -866,7 +846,6 @@ app.post('/v1/messages', async (req, res) => {
866
846
  }
867
847
 
868
848
  } else {
869
- // Handle non-streaming response
870
849
  const response = await sendMessage(request, accountManager, FALLBACK_ENABLED);
871
850
  usageStats.track(response?.model || modelId);
872
851
  res.json(response);
@@ -886,6 +865,7 @@ app.post('/v1/messages', async (req, res) => {
886
865
  await forceRefresh();
887
866
  errorMessage = 'Token was expired and has been refreshed. Please retry your request.';
888
867
  } catch (refreshError) {
868
+ logger.warn(`[API] Token refresh failed: ${refreshError.message}`);
889
869
  errorMessage = 'Could not refresh token. Make sure Antigravity is running.';
890
870
  }
891
871
  }
@@ -917,8 +897,7 @@ app.post('/v1/messages', async (req, res) => {
917
897
  */
918
898
  usageStats.setupRoutes(app);
919
899
 
920
- app.use('*', (req, res) => {
921
- // Log 404s (use originalUrl since wildcard strips req.path)
900
+ app.use((req, res) => {
922
901
  if (logger.isDebugEnabled) {
923
902
  logger.debug(`[API] 404 Not Found: ${req.method} ${req.originalUrl}`);
924
903
  }
@@ -83,7 +83,7 @@ export async function updateClaudeConfig(updates) {
83
83
  try {
84
84
  await fs.mkdir(configDir, { recursive: true });
85
85
  } catch (error) {
86
- // Ignore if exists
86
+ logger.debug(`[ClaudeConfig] Could not create ${configDir}: ${error.message}`);
87
87
  }
88
88
 
89
89
  // 4. Write back to file
@@ -112,7 +112,7 @@ export async function replaceClaudeConfig(config) {
112
112
  try {
113
113
  await fs.mkdir(configDir, { recursive: true });
114
114
  } catch (error) {
115
- // Ignore if exists
115
+ logger.debug(`[ClaudeConfig] Could not create ${configDir}: ${error.message}`);
116
116
  }
117
117
 
118
118
  // 2. Write config directly (no merge)
@@ -153,10 +153,6 @@ function isObject(item) {
153
153
  return (item && typeof item === 'object' && !Array.isArray(item));
154
154
  }
155
155
 
156
- // ==========================================
157
- // Claude CLI Presets
158
- // ==========================================
159
-
160
156
  /**
161
157
  * Get the path to the presets file
162
158
  * @returns {string} Absolute path to claude-presets.json
@@ -64,14 +64,12 @@ export function rebuildModule(packagePath) {
64
64
  try {
65
65
  logger.info(`[NativeModule] Rebuilding native module at: ${packagePath}`);
66
66
 
67
- // Run npm rebuild in the package directory
68
67
  const output = execSync('npm rebuild', {
69
68
  cwd: packagePath,
70
69
  stdio: 'pipe', // Capture output instead of printing
71
70
  timeout: 120000 // 2 minute timeout
72
71
  });
73
72
 
74
- // Log rebuild output for debugging
75
73
  const outputStr = output?.toString().trim();
76
74
  if (outputStr) {
77
75
  logger.debug(`[NativeModule] Rebuild output:\n${outputStr}`);
@@ -140,7 +138,6 @@ export function clearRequireCache(modulePath, cache, visited = new Set()) {
140
138
  }
141
139
  }
142
140
 
143
- // Remove from parent's children array
144
141
  if (mod.parent && mod.parent.children) {
145
142
  const idx = mod.parent.children.indexOf(mod);
146
143
  if (idx !== -1) {
@@ -148,7 +145,6 @@ export function clearRequireCache(modulePath, cache, visited = new Set()) {
148
145
  }
149
146
  }
150
147
 
151
- // Delete from cache
152
148
  delete cache[modulePath];
153
149
  }
154
150
 
@@ -1,13 +1,4 @@
1
- /**
2
- * HTTP Proxy Support
3
- *
4
- * Configures global fetch to use HTTP proxy from environment variables.
5
- * Supports: http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY
6
- *
7
- * This module should be imported at the very beginning of the application
8
- * entry point (src/index.js) before any fetch calls are made.
9
- */
10
-
1
+ // Routes global fetch through http(s)_proxy env vars. Import before any fetch call (see src/index.js).
11
2
  import { ProxyAgent, setGlobalDispatcher } from 'undici';
12
3
  import { logger } from './logger.js';
13
4
 
@@ -27,7 +27,7 @@ import { config } from '../config.js';
27
27
 
28
28
  // Fallback for User-Agent version (ideVersion in product.json), used only when
29
29
  // no installed Antigravity version is detected
30
- const FALLBACK_USER_AGENT_VERSION = process.env.FALLBACK_ANTIGRAVITY_VERSION || '2.25.0';
30
+ const FALLBACK_USER_AGENT_VERSION = process.env.FALLBACK_ANTIGRAVITY_VERSION || '2.32.0';
31
31
 
32
32
  // Detected versions must be above this to be used (older installs are ignored)
33
33
  const MIN_DETECTED_USER_AGENT_VERSION = '2.0.3';
@@ -104,7 +104,7 @@ function getProductJson() {
104
104
  }
105
105
  }
106
106
  } catch (e) {
107
- // Continue to next path
107
+ logDetectionFailure(`Reading ${p}`, e);
108
108
  }
109
109
  }
110
110
 
@@ -112,6 +112,15 @@ function getProductJson() {
112
112
  return null;
113
113
  }
114
114
 
115
+ /**
116
+ * Debug-log a failed detection step (lazy import to avoid circular deps).
117
+ */
118
+ function logDetectionFailure(step, err) {
119
+ import('./logger.js').then(({ logger }) => {
120
+ logger.debug(`[VersionDetector] ${step} failed: ${err?.message ?? err}`);
121
+ }).catch(() => {});
122
+ }
123
+
115
124
  /**
116
125
  * Log version detection results once at startup (lazy import to avoid circular deps).
117
126
  */
@@ -188,7 +197,7 @@ function getUserAgentVersionConfig() {
188
197
  localVersion = getVersionWindows();
189
198
  }
190
199
  } catch (error) {
191
- // Silently fail and use fallback
200
+ logDetectionFailure('OS version detection', error);
192
201
  }
193
202
 
194
203
  return resolveUserAgentVersion({ localVersion });
@@ -246,7 +255,7 @@ function getVersionMacos() {
246
255
  return version;
247
256
  }
248
257
  } catch (e) {
249
- // plutil failed or file not found
258
+ logDetectionFailure('plutil', e);
250
259
  }
251
260
  return null;
252
261
  }
@@ -273,7 +282,7 @@ function getVersionWindows() {
273
282
  }
274
283
  }
275
284
  } catch (e) {
276
- // PowerShell or path issues
285
+ logDetectionFailure('PowerShell version lookup', e);
277
286
  }
278
287
  return null;
279
288
  }