antigravity-claude-proxy 2.8.6 → 2.9.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/package.json +3 -2
- package/public/views/logs.html +2 -1
- package/src/cloudcode/model-api.js +17 -2
- package/src/cloudcode/model-registry.js +96 -0
- package/src/config.js +5 -0
- package/src/constants.js +9 -3
- package/src/format/request-converter.js +16 -2
- package/src/format/schema-sanitizer.js +16 -1
- package/src/utils/version-detector.js +69 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antigravity-claude-proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Proxy server to use Antigravity's Claude models with Claude Code CLI",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"test:emptyretry": "node tests/test-empty-response-retry.cjs",
|
|
38
38
|
"test:sanitizer": "node tests/test-schema-sanitizer.cjs",
|
|
39
39
|
"test:strategies": "node tests/test-strategies.cjs",
|
|
40
|
-
"test:cache-control": "node tests/test-cache-control.cjs"
|
|
40
|
+
"test:cache-control": "node tests/test-cache-control.cjs",
|
|
41
|
+
"test:billing-header": "node tests/test-billing-header.cjs"
|
|
41
42
|
},
|
|
42
43
|
"keywords": [
|
|
43
44
|
"claude",
|
package/public/views/logs.html
CHANGED
|
@@ -89,7 +89,8 @@
|
|
|
89
89
|
|
|
90
90
|
<!-- Message: Clean & High Contrast -->
|
|
91
91
|
<span class="text-zinc-300 break-all group-hover:text-white transition-colors flex-1"
|
|
92
|
-
|
|
92
|
+
style="white-space: pre-wrap"
|
|
93
|
+
x-text="Redact.logMessage(log.message)"></span>
|
|
93
94
|
</div>
|
|
94
95
|
</template>
|
|
95
96
|
<!-- Blinking Cursor -->
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '../constants.js';
|
|
16
16
|
import { logger } from '../utils/logger.js';
|
|
17
17
|
import { throttledFetch } from '../utils/helpers.js';
|
|
18
|
+
import { recordModels } from './model-registry.js';
|
|
18
19
|
|
|
19
20
|
// Model validation cache
|
|
20
21
|
const modelCache = {
|
|
@@ -24,7 +25,7 @@ const modelCache = {
|
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
* Check if a model is supported (Claude or Gemini)
|
|
28
|
+
* Check if a model is supported (Claude or Gemini, by name or backend provider)
|
|
28
29
|
* @param {string} modelId - Model ID to check
|
|
29
30
|
* @returns {boolean} True if model is supported
|
|
30
31
|
*/
|
|
@@ -50,6 +51,17 @@ function isOldGemini(modelId) {
|
|
|
50
51
|
return !!match && parseFloat(match[1]) < 3.5;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Whether a model id is a backend placeholder for a model the account can't
|
|
56
|
+
* use (e.g. 'gemini-4-argon-disabled' on free-tier accounts), hidden from the
|
|
57
|
+
* /v1/models listing since requests to it can't succeed.
|
|
58
|
+
* @param {string} modelId
|
|
59
|
+
* @returns {boolean}
|
|
60
|
+
*/
|
|
61
|
+
function isDisabledPlaceholder(modelId) {
|
|
62
|
+
return modelId.endsWith('-disabled');
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
/**
|
|
54
66
|
* List available models in Anthropic API format
|
|
55
67
|
* Fetches models dynamically from the Cloud Code API
|
|
@@ -63,6 +75,7 @@ export async function listModels(token) {
|
|
|
63
75
|
return { object: 'list', data: [] };
|
|
64
76
|
}
|
|
65
77
|
|
|
78
|
+
recordModels(data);
|
|
66
79
|
const supportedEntries = Object.entries(data.models).filter(([modelId]) => isSupportedModel(modelId));
|
|
67
80
|
|
|
68
81
|
// Warm the model validation cache with every supported id, unfiltered -
|
|
@@ -73,7 +86,7 @@ export async function listModels(token) {
|
|
|
73
86
|
modelCache.lastFetched = Date.now();
|
|
74
87
|
|
|
75
88
|
const modelList = supportedEntries
|
|
76
|
-
.filter(([modelId]) => !isOldGemini(modelId))
|
|
89
|
+
.filter(([modelId]) => !isOldGemini(modelId) && !isDisabledPlaceholder(modelId))
|
|
77
90
|
.map(([modelId, modelData]) => ({
|
|
78
91
|
id: modelId,
|
|
79
92
|
object: 'model',
|
|
@@ -148,6 +161,7 @@ export async function fetchAvailableModels(token, projectId = null) {
|
|
|
148
161
|
export async function getModelQuotas(token, projectId = null) {
|
|
149
162
|
const data = await fetchAvailableModels(token, projectId);
|
|
150
163
|
if (!data || !data.models) return {};
|
|
164
|
+
recordModels(data);
|
|
151
165
|
|
|
152
166
|
const quotas = {};
|
|
153
167
|
for (const [modelId, modelData] of Object.entries(data.models)) {
|
|
@@ -319,6 +333,7 @@ async function populateModelCache(token, projectId = null) {
|
|
|
319
333
|
try {
|
|
320
334
|
const data = await fetchAvailableModels(token, projectId);
|
|
321
335
|
if (data && data.models) {
|
|
336
|
+
recordModels(data);
|
|
322
337
|
const validIds = Object.keys(data.models).filter(modelId => isSupportedModel(modelId));
|
|
323
338
|
modelCache.validModels = new Set(validIds);
|
|
324
339
|
modelCache.lastFetched = Date.now();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Registry
|
|
3
|
+
*
|
|
4
|
+
* Records per-model metadata from Cloud Code's fetchAvailableModels response so
|
|
5
|
+
* model family / thinking support can be resolved from what the backend reports
|
|
6
|
+
* (apiProvider, supportsThinking) when the model id itself doesn't say - e.g. a
|
|
7
|
+
* new model shipped under a codename without "gemini" or "claude" in its id.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately has no imports: constants.js reads from it, and model-api.js
|
|
10
|
+
* (which imports constants.js) writes to it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const registry = new Map();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Map a Cloud Code apiProvider value to a model family.
|
|
17
|
+
* @param {string} apiProvider - e.g. 'API_PROVIDER_GOOGLE_GEMINI'
|
|
18
|
+
* @returns {'claude' | 'gemini' | null}
|
|
19
|
+
*/
|
|
20
|
+
function familyFromProvider(apiProvider) {
|
|
21
|
+
const provider = String(apiProvider || '').toUpperCase();
|
|
22
|
+
if (provider.includes('GOOGLE_GEMINI')) return 'gemini';
|
|
23
|
+
if (provider.includes('ANTHROPIC')) return 'claude';
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Collect the ids the backend offers as selectable agent models
|
|
29
|
+
* (agentModelSorts groups and tieredModelIds). Internal/tab-completion
|
|
30
|
+
* models (tab_*, chat_*) never appear in these lists.
|
|
31
|
+
* @param {Object} data - fetchAvailableModels response
|
|
32
|
+
* @returns {Set<string>}
|
|
33
|
+
*/
|
|
34
|
+
function collectAgentModelIds(data) {
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
for (const sort of data?.agentModelSorts || []) {
|
|
37
|
+
for (const group of sort?.groups || []) {
|
|
38
|
+
for (const id of group?.modelIds || []) ids.add(id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const list of Object.values(data?.tieredModelIds || {})) {
|
|
42
|
+
if (Array.isArray(list)) list.forEach(id => ids.add(id));
|
|
43
|
+
}
|
|
44
|
+
return ids;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether the model id names its family itself (legacy name-based detection).
|
|
49
|
+
* @param {string} modelId
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
function hasFamilyInName(modelId) {
|
|
53
|
+
const lower = (modelId || '').toLowerCase();
|
|
54
|
+
return lower.includes('claude') || lower.includes('gemini');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Record model metadata from a fetchAvailableModels response.
|
|
59
|
+
* Ids whose name already identifies the family are always recorded; other ids
|
|
60
|
+
* only when the backend lists them as agent models with a Gemini/Anthropic
|
|
61
|
+
* provider, so internal models (tab_*, chat_*) and other providers (gpt-oss)
|
|
62
|
+
* stay excluded.
|
|
63
|
+
* @param {Object} data - fetchAvailableModels response
|
|
64
|
+
*/
|
|
65
|
+
export function recordModels(data) {
|
|
66
|
+
if (!data || !data.models) return;
|
|
67
|
+
const agentIds = collectAgentModelIds(data);
|
|
68
|
+
|
|
69
|
+
for (const [modelId, modelData] of Object.entries(data.models)) {
|
|
70
|
+
const family = familyFromProvider(modelData?.apiProvider);
|
|
71
|
+
if (!hasFamilyInName(modelId) && !(family && agentIds.has(modelId))) continue;
|
|
72
|
+
|
|
73
|
+
registry.set(modelId, {
|
|
74
|
+
family,
|
|
75
|
+
supportsThinking: modelData?.supportsThinking === true,
|
|
76
|
+
maxOutputTokens: modelData?.maxOutputTokens ?? null,
|
|
77
|
+
displayName: modelData?.displayName || null
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get recorded metadata for a model id.
|
|
84
|
+
* @param {string} modelId
|
|
85
|
+
* @returns {{family: 'claude'|'gemini'|null, supportsThinking: boolean, maxOutputTokens: number|null, displayName: string|null} | undefined}
|
|
86
|
+
*/
|
|
87
|
+
export function getRegisteredModel(modelId) {
|
|
88
|
+
return registry.get(modelId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear all recorded metadata (used by tests).
|
|
93
|
+
*/
|
|
94
|
+
export function clearModelRegistry() {
|
|
95
|
+
registry.clear();
|
|
96
|
+
}
|
package/src/config.js
CHANGED
|
@@ -53,6 +53,11 @@ const DEFAULT_CONFIG = {
|
|
|
53
53
|
switchAccountDelayMs: 5000, // Delay before switching accounts on rate limit
|
|
54
54
|
capacityBackoffTiersMs: [5000, 10000, 20000, 30000, 60000], // Progressive backoff tiers for capacity exhaustion
|
|
55
55
|
modelMapping: {},
|
|
56
|
+
// Antigravity client version sent to Cloud Code (X-Client-Version / User-Agent).
|
|
57
|
+
// Empty = auto-detect from local Antigravity install, then hardcoded fallback.
|
|
58
|
+
// The backend hides newer models from outdated client versions.
|
|
59
|
+
antigravityClientVersion: '',
|
|
60
|
+
antigravityIdeVersion: '',
|
|
56
61
|
// Account selection strategy configuration
|
|
57
62
|
accountSelection: {
|
|
58
63
|
strategy: 'hybrid', // 'sticky' | 'round-robin' | 'hybrid'
|
package/src/constants.js
CHANGED
|
@@ -7,6 +7,7 @@ import { homedir, platform, arch } from 'os';
|
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { config } from './config.js';
|
|
9
9
|
import { generateSmartUserAgent, getClientVersion } from './utils/version-detector.js';
|
|
10
|
+
import { getRegisteredModel } from './cloudcode/model-registry.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Get the Antigravity database path based on the current platform.
|
|
@@ -29,7 +30,7 @@ function getAntigravityDbPath() {
|
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Generate platform-specific User-Agent string.
|
|
32
|
-
* @returns {string} User-Agent in format "antigravity/version os/arch"
|
|
33
|
+
* @returns {string} User-Agent in format "antigravity/hub/version os/arch"
|
|
33
34
|
*/
|
|
34
35
|
export function getPlatformUserAgent() {
|
|
35
36
|
return generateSmartUserAgent();
|
|
@@ -217,6 +218,7 @@ export const MODEL_VALIDATION_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
217
218
|
|
|
218
219
|
/**
|
|
219
220
|
* Get the model family from model name (dynamic detection, no hardcoded list).
|
|
221
|
+
* Ids without a family in their name resolve via backend metadata (model-registry.js).
|
|
220
222
|
* @param {string} modelName - The model name from the request
|
|
221
223
|
* @returns {'claude' | 'gemini' | 'unknown'} The model family
|
|
222
224
|
*/
|
|
@@ -224,7 +226,8 @@ export function getModelFamily(modelName) {
|
|
|
224
226
|
const lower = (modelName || '').toLowerCase();
|
|
225
227
|
if (lower.includes('claude')) return 'claude';
|
|
226
228
|
if (lower.includes('gemini')) return 'gemini';
|
|
227
|
-
|
|
229
|
+
// Fall back to the provider reported by the backend (codename model ids)
|
|
230
|
+
return getRegisteredModel(modelName)?.family || 'unknown';
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
/**
|
|
@@ -242,8 +245,11 @@ export function isThinkingModel(modelName) {
|
|
|
242
245
|
// Check for gemini-3 or higher (e.g., gemini-3, gemini-3.5, gemini-4, etc.)
|
|
243
246
|
const versionMatch = lower.match(/gemini-(\d+)/);
|
|
244
247
|
if (versionMatch && parseInt(versionMatch[1], 10) >= 3) return true;
|
|
248
|
+
return false;
|
|
245
249
|
}
|
|
246
|
-
return false;
|
|
250
|
+
if (lower.includes('claude')) return false;
|
|
251
|
+
// Codename ids: trust the backend's supportsThinking flag
|
|
252
|
+
return getRegisteredModel(modelName)?.supportsThinking === true;
|
|
247
253
|
}
|
|
248
254
|
|
|
249
255
|
// Google OAuth configuration (from opencode-antigravity-auth)
|
|
@@ -24,6 +24,17 @@ import {
|
|
|
24
24
|
} from './thinking-utils.js';
|
|
25
25
|
import { logger } from '../utils/logger.js';
|
|
26
26
|
|
|
27
|
+
// Claude Code injects a billing pseudo-header as a system prompt block, e.g.
|
|
28
|
+
// "x-anthropic-billing-header: cc_version=2.1.274.834; cc_entrypoint=sdk-cli;".
|
|
29
|
+
// Cloud Code API answers any request containing it with a 429 RESOURCE_EXHAUSTED
|
|
30
|
+
// even when quota remains (Issue #375).
|
|
31
|
+
const BILLING_HEADER_LINE = /^x-anthropic-billing-header:[^\n]*\n?/gim;
|
|
32
|
+
|
|
33
|
+
function stripBillingHeader(text) {
|
|
34
|
+
if (typeof text !== 'string' || !/x-anthropic-billing-header:/i.test(text)) return text;
|
|
35
|
+
return text.replace(BILLING_HEADER_LINE, '').trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* Convert Anthropic Messages API request to the format expected by Cloud Code
|
|
29
40
|
*
|
|
@@ -56,15 +67,18 @@ export function convertAnthropicToGoogle(anthropicRequest) {
|
|
|
56
67
|
if (system) {
|
|
57
68
|
let systemParts = [];
|
|
58
69
|
if (typeof system === 'string') {
|
|
59
|
-
systemParts = [{ text: system }];
|
|
70
|
+
systemParts = [{ text: stripBillingHeader(system) }];
|
|
60
71
|
} else if (Array.isArray(system)) {
|
|
61
72
|
// Filter for text blocks as system prompts are usually text
|
|
62
73
|
// Anthropic supports text blocks in system prompts
|
|
63
74
|
systemParts = system
|
|
64
75
|
.filter(block => block.type === 'text')
|
|
65
|
-
.map(block => ({ text: block.text }));
|
|
76
|
+
.map(block => ({ text: stripBillingHeader(block.text) }));
|
|
66
77
|
}
|
|
67
78
|
|
|
79
|
+
// Drop parts left empty once the billing header is stripped
|
|
80
|
+
systemParts = systemParts.filter(part => part.text);
|
|
81
|
+
|
|
68
82
|
if (systemParts.length > 0) {
|
|
69
83
|
googleRequest.systemInstruction = {
|
|
70
84
|
parts: systemParts
|
|
@@ -518,7 +518,16 @@ export function sanitizeSchema(schema) {
|
|
|
518
518
|
for (const [key, value] of Object.entries(schema)) {
|
|
519
519
|
// Convert "const" to "enum" for compatibility
|
|
520
520
|
if (key === 'const') {
|
|
521
|
-
|
|
521
|
+
// Google's API rejects non-string enum values (TYPE_STRING).
|
|
522
|
+
// Convert booleans/numbers to strings, or skip if the value is
|
|
523
|
+
// not a simple scalar.
|
|
524
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
525
|
+
sanitized.enum = [String(value)];
|
|
526
|
+
} else {
|
|
527
|
+
// For complex const values, add as description hint instead
|
|
528
|
+
// local fix: `sanitized` is a const; merge the hint instead of reassigning
|
|
529
|
+
Object.assign(sanitized, appendDescriptionHint(sanitized, `Must be: ${JSON.stringify(value)}`));
|
|
530
|
+
}
|
|
522
531
|
continue;
|
|
523
532
|
}
|
|
524
533
|
|
|
@@ -527,6 +536,12 @@ export function sanitizeSchema(schema) {
|
|
|
527
536
|
continue;
|
|
528
537
|
}
|
|
529
538
|
|
|
539
|
+
// Sanitize enum values: Google requires TYPE_STRING for enum entries
|
|
540
|
+
if (key === 'enum' && Array.isArray(value)) {
|
|
541
|
+
sanitized.enum = value.map(v => (typeof v === 'string') ? v : String(v));
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
|
|
530
545
|
if (key === 'properties' && value && typeof value === 'object') {
|
|
531
546
|
sanitized.properties = {};
|
|
532
547
|
for (const [propKey, propValue] of Object.entries(value)) {
|
|
@@ -2,6 +2,7 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
import { platform, homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { existsSync, readFileSync } from 'fs';
|
|
5
|
+
import { config } from '../config.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Intelligent Version Detection for Antigravity
|
|
@@ -13,13 +14,23 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
13
14
|
*
|
|
14
15
|
* Detection priority (for each):
|
|
15
16
|
* 1. Environment variable override
|
|
16
|
-
* 2.
|
|
17
|
-
* 3.
|
|
18
|
-
* 4.
|
|
17
|
+
* 2. config.json (antigravityClientVersion / antigravityIdeVersion)
|
|
18
|
+
* 3. product.json from local Antigravity app
|
|
19
|
+
* 4. OS-specific detection (macOS plist / Windows exe)
|
|
20
|
+
* 5. Hardcoded fallback
|
|
21
|
+
*
|
|
22
|
+
* The Cloud Code backend gates newer models (e.g. gemini-4-argon-*) on the
|
|
23
|
+
* User-Agent: it must use the Antigravity Hub format ("antigravity/hub/<version>")
|
|
24
|
+
* with a recent enough version, so an outdated fallback can hide models that the
|
|
25
|
+
* real Antigravity client lists.
|
|
19
26
|
*/
|
|
20
27
|
|
|
21
|
-
// Fallback for User-Agent version (ideVersion in product.json)
|
|
22
|
-
|
|
28
|
+
// Fallback for User-Agent version (ideVersion in product.json), used only when
|
|
29
|
+
// no installed Antigravity version is detected
|
|
30
|
+
const FALLBACK_USER_AGENT_VERSION = process.env.FALLBACK_ANTIGRAVITY_VERSION || '2.25.0';
|
|
31
|
+
|
|
32
|
+
// Detected versions must be above this to be used (older installs are ignored)
|
|
33
|
+
const MIN_DETECTED_USER_AGENT_VERSION = '2.0.3';
|
|
23
34
|
|
|
24
35
|
// Fallback for X-Client-Version (top-level "version" in product.json)
|
|
25
36
|
// Can be overridden via ANTIGRAVITY_CLIENT_VERSION_FALLBACK env var
|
|
@@ -110,42 +121,49 @@ function logVersionInfo(version, source) {
|
|
|
110
121
|
|
|
111
122
|
import('./logger.js').then(({ logger }) => {
|
|
112
123
|
if (source === 'fallback') {
|
|
113
|
-
logger.warn(`X-Client-Version: using hardcoded fallback ${version} — product.json not found. Set
|
|
124
|
+
logger.warn(`X-Client-Version: using hardcoded fallback ${version} — product.json not found. Newer models may be hidden by the backend. Set "antigravityClientVersion" in config.json or the ANTIGRAVITY_CLIENT_VERSION env var to your Antigravity version.`);
|
|
114
125
|
} else {
|
|
115
126
|
logger.debug(`X-Client-Version: ${version} (source: ${source})`);
|
|
116
127
|
}
|
|
117
128
|
}).catch(() => {});
|
|
118
129
|
}
|
|
119
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Resolve the X-Client-Version from its candidate sources (pure, for testing).
|
|
133
|
+
* Priority: env var > config.json > product.json "version" > hardcoded fallback
|
|
134
|
+
* @param {{envVersion?: string, configVersion?: string, productVersion?: string}} sources
|
|
135
|
+
* @returns {{version: string, source: 'env'|'config'|'product.json'|'fallback'}}
|
|
136
|
+
*/
|
|
137
|
+
export function resolveClientVersion({ envVersion, configVersion, productVersion } = {}) {
|
|
138
|
+
if (envVersion) return { version: envVersion, source: 'env' };
|
|
139
|
+
if (configVersion) return { version: String(configVersion), source: 'config' };
|
|
140
|
+
if (productVersion) return { version: productVersion, source: 'product.json' };
|
|
141
|
+
return { version: FALLBACK_CLIENT_VERSION, source: 'fallback' };
|
|
142
|
+
}
|
|
143
|
+
|
|
120
144
|
/**
|
|
121
145
|
* Get the X-Client-Version value for API requests.
|
|
122
|
-
* Priority: ANTIGRAVITY_CLIENT_VERSION env var >
|
|
146
|
+
* Priority: ANTIGRAVITY_CLIENT_VERSION env var > config.json "antigravityClientVersion"
|
|
147
|
+
* > product.json "version" > hardcoded fallback
|
|
123
148
|
* @returns {string} Version string (e.g. "1.110.0")
|
|
124
149
|
*/
|
|
125
150
|
export function getClientVersion() {
|
|
126
151
|
if (cachedClientVersion) return cachedClientVersion;
|
|
127
152
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (product?.version) {
|
|
136
|
-
cachedClientVersion = product.version;
|
|
137
|
-
logVersionInfo(cachedClientVersion, 'product.json');
|
|
138
|
-
return cachedClientVersion;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
cachedClientVersion = FALLBACK_CLIENT_VERSION;
|
|
142
|
-
logVersionInfo(cachedClientVersion, 'fallback');
|
|
153
|
+
const { version, source } = resolveClientVersion({
|
|
154
|
+
envVersion: process.env.ANTIGRAVITY_CLIENT_VERSION,
|
|
155
|
+
configVersion: config?.antigravityClientVersion,
|
|
156
|
+
productVersion: getProductJson()?.version
|
|
157
|
+
});
|
|
158
|
+
cachedClientVersion = version;
|
|
159
|
+
logVersionInfo(cachedClientVersion, source);
|
|
143
160
|
return cachedClientVersion;
|
|
144
161
|
}
|
|
145
162
|
|
|
146
163
|
/**
|
|
147
164
|
* Get the User-Agent version string.
|
|
148
|
-
* Priority: FALLBACK_ANTIGRAVITY_VERSION env var >
|
|
165
|
+
* Priority: FALLBACK_ANTIGRAVITY_VERSION env var > config.json "antigravityIdeVersion"
|
|
166
|
+
* > product.json "ideVersion" > OS detection > fallback
|
|
149
167
|
* @returns {{ version: string, source: string }}
|
|
150
168
|
*/
|
|
151
169
|
function getUserAgentVersionConfig() {
|
|
@@ -153,34 +171,51 @@ function getUserAgentVersionConfig() {
|
|
|
153
171
|
return { version: process.env.FALLBACK_ANTIGRAVITY_VERSION, source: 'env' };
|
|
154
172
|
}
|
|
155
173
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return { version: product.ideVersion, source: 'product.json' };
|
|
174
|
+
if (config?.antigravityIdeVersion) {
|
|
175
|
+
return { version: String(config.antigravityIdeVersion), source: 'config' };
|
|
159
176
|
}
|
|
160
177
|
|
|
178
|
+
const fromProduct = resolveUserAgentVersion({ productVersion: getProductJson()?.ideVersion });
|
|
179
|
+
if (fromProduct.source !== 'fallback') return fromProduct;
|
|
180
|
+
|
|
161
181
|
// OS-specific detection (legacy — reads app binary metadata directly)
|
|
162
182
|
const os = platform();
|
|
163
|
-
let
|
|
183
|
+
let localVersion = null;
|
|
164
184
|
try {
|
|
165
185
|
if (os === 'darwin') {
|
|
166
|
-
|
|
186
|
+
localVersion = getVersionMacos();
|
|
167
187
|
} else if (os === 'win32') {
|
|
168
|
-
|
|
188
|
+
localVersion = getVersionWindows();
|
|
169
189
|
}
|
|
170
190
|
} catch (error) {
|
|
171
191
|
// Silently fail and use fallback
|
|
172
192
|
}
|
|
173
193
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
194
|
+
return resolveUserAgentVersion({ localVersion });
|
|
195
|
+
}
|
|
177
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Resolve the User-Agent version from detected installs (pure, for testing).
|
|
199
|
+
* A detected version is used as-is; the fallback applies only when nothing
|
|
200
|
+
* usable was detected.
|
|
201
|
+
* @param {{productVersion?: string, localVersion?: string}} detected
|
|
202
|
+
* @returns {{version: string, source: 'product.json'|'local'|'fallback'}}
|
|
203
|
+
*/
|
|
204
|
+
export function resolveUserAgentVersion({ productVersion, localVersion } = {}) {
|
|
205
|
+
if (productVersion && isVersionHigher(productVersion, MIN_DETECTED_USER_AGENT_VERSION)) {
|
|
206
|
+
return { version: productVersion, source: 'product.json' };
|
|
207
|
+
}
|
|
208
|
+
if (localVersion && isVersionHigher(localVersion, MIN_DETECTED_USER_AGENT_VERSION)) {
|
|
209
|
+
return { version: localVersion, source: 'local' };
|
|
210
|
+
}
|
|
178
211
|
return { version: FALLBACK_USER_AGENT_VERSION, source: 'fallback' };
|
|
179
212
|
}
|
|
180
213
|
|
|
181
214
|
/**
|
|
182
215
|
* Generate a simplified User-Agent string used by the Antigravity binary.
|
|
183
|
-
* Format: "antigravity/version os/arch"
|
|
216
|
+
* Format: "antigravity/hub/version os/arch" (Antigravity 2.x Hub family; the
|
|
217
|
+
* backend hides newer models such as gemini-4-argon-* from the legacy
|
|
218
|
+
* "antigravity/version os/arch" format)
|
|
184
219
|
* @returns {string} The User-Agent string
|
|
185
220
|
*/
|
|
186
221
|
export function generateSmartUserAgent() {
|
|
@@ -192,7 +227,7 @@ export function generateSmartUserAgent() {
|
|
|
192
227
|
|
|
193
228
|
const osName = os === 'darwin' ? 'darwin' : (os === 'win32' ? 'win32' : 'linux');
|
|
194
229
|
|
|
195
|
-
cachedUserAgent = `antigravity/${version} ${osName}/${architecture}`;
|
|
230
|
+
cachedUserAgent = `antigravity/hub/${version} ${osName}/${architecture}`;
|
|
196
231
|
return cachedUserAgent;
|
|
197
232
|
}
|
|
198
233
|
|