antigravity-claude-proxy 2.8.4 → 2.8.6
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 +8 -8
- package/package.json +1 -1
- package/public/js/data-store.js +4 -4
- package/src/cloudcode/model-api.js +28 -6
- package/src/cloudcode/request-builder.js +45 -2
- package/src/constants.js +12 -13
- package/src/format/schema-sanitizer.js +37 -0
- package/src/utils/version-detector.js +134 -24
package/README.md
CHANGED
|
@@ -183,9 +183,9 @@ Add this configuration:
|
|
|
183
183
|
"ANTHROPIC_BASE_URL": "http://localhost:8080",
|
|
184
184
|
"ANTHROPIC_MODEL": "claude-opus-4-6-thinking",
|
|
185
185
|
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-6-thinking",
|
|
186
|
-
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6
|
|
186
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
|
|
187
187
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-sonnet-4-6",
|
|
188
|
-
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6
|
|
188
|
+
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6",
|
|
189
189
|
"ENABLE_EXPERIMENTAL_MCP_CLI": "true"
|
|
190
190
|
}
|
|
191
191
|
}
|
|
@@ -198,11 +198,11 @@ Or to use Gemini models:
|
|
|
198
198
|
"env": {
|
|
199
199
|
"ANTHROPIC_AUTH_TOKEN": "test",
|
|
200
200
|
"ANTHROPIC_BASE_URL": "http://localhost:8080",
|
|
201
|
-
"ANTHROPIC_MODEL": "gemini-3.1-pro-
|
|
202
|
-
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gemini-3.1-pro-
|
|
203
|
-
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gemini-3-flash
|
|
204
|
-
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-3-flash
|
|
205
|
-
"CLAUDE_CODE_SUBAGENT_MODEL": "gemini-3-flash
|
|
201
|
+
"ANTHROPIC_MODEL": "gemini-3.1-pro-low",
|
|
202
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gemini-3.1-pro-low",
|
|
203
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gemini-3.5-flash-low",
|
|
204
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-3.5-flash-low",
|
|
205
|
+
"CLAUDE_CODE_SUBAGENT_MODEL": "gemini-3.5-flash-low",
|
|
206
206
|
"ENABLE_EXPERIMENTAL_MCP_CLI": "true"
|
|
207
207
|
}
|
|
208
208
|
}
|
|
@@ -339,4 +339,4 @@ MIT
|
|
|
339
339
|
|
|
340
340
|
## Star History
|
|
341
341
|
|
|
342
|
-
[](https://star-history.dera.page/#badrisnarayanan/antigravity-claude-proxy&type=date&legend=top-left)
|
package/package.json
CHANGED
package/public/js/data-store.js
CHANGED
|
@@ -436,11 +436,11 @@ document.addEventListener('alpine:init', () => {
|
|
|
436
436
|
_generatePlaceholderData() {
|
|
437
437
|
const models = [
|
|
438
438
|
'claude-opus-4-6-thinking',
|
|
439
|
-
'claude-sonnet-4-6-thinking',
|
|
440
439
|
'claude-sonnet-4-6',
|
|
441
|
-
'gemini-3.
|
|
442
|
-
'gemini-3.
|
|
443
|
-
'gemini-3-flash'
|
|
440
|
+
'gemini-3.8-flash-tiered',
|
|
441
|
+
'gemini-3.6-flash-high',
|
|
442
|
+
'gemini-3.6-flash-low',
|
|
443
|
+
'gemini-3.5-flash-low'
|
|
444
444
|
];
|
|
445
445
|
|
|
446
446
|
const tiers = ['ultra', 'pro', 'pro', 'free'];
|
|
@@ -33,6 +33,23 @@ function isSupportedModel(modelId) {
|
|
|
33
33
|
return family === 'claude' || family === 'gemini';
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Whether a model id is an old Gemini generation (version below 3.5),
|
|
38
|
+
* hidden from the /v1/models listing in favor of the current line. Claude
|
|
39
|
+
* ids aren't versioned this way and are unaffected.
|
|
40
|
+
* @param {string} modelId
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isOldGemini(modelId) {
|
|
44
|
+
// 'gemini-pro-agent' carries no version number in its id at all - it's
|
|
45
|
+
// an unversioned duplicate alias of gemini-3.1-pro-high, so it's always
|
|
46
|
+
// old regardless of what the version regex below would say.
|
|
47
|
+
if (modelId === 'gemini-pro-agent') return true;
|
|
48
|
+
|
|
49
|
+
const match = modelId.match(/^gemini-(\d+(?:\.\d+)?)/);
|
|
50
|
+
return !!match && parseFloat(match[1]) < 3.5;
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
/**
|
|
37
54
|
* List available models in Anthropic API format
|
|
38
55
|
* Fetches models dynamically from the Cloud Code API
|
|
@@ -46,8 +63,17 @@ export async function listModels(token) {
|
|
|
46
63
|
return { object: 'list', data: [] };
|
|
47
64
|
}
|
|
48
65
|
|
|
49
|
-
const
|
|
50
|
-
|
|
66
|
+
const supportedEntries = Object.entries(data.models).filter(([modelId]) => isSupportedModel(modelId));
|
|
67
|
+
|
|
68
|
+
// Warm the model validation cache with every supported id, unfiltered -
|
|
69
|
+
// isValidModel() (the actual request-time gate) must keep accepting an
|
|
70
|
+
// id a client already has configured even if it's hidden from the
|
|
71
|
+
// listing below.
|
|
72
|
+
modelCache.validModels = new Set(supportedEntries.map(([modelId]) => modelId));
|
|
73
|
+
modelCache.lastFetched = Date.now();
|
|
74
|
+
|
|
75
|
+
const modelList = supportedEntries
|
|
76
|
+
.filter(([modelId]) => !isOldGemini(modelId))
|
|
51
77
|
.map(([modelId, modelData]) => ({
|
|
52
78
|
id: modelId,
|
|
53
79
|
object: 'model',
|
|
@@ -56,10 +82,6 @@ export async function listModels(token) {
|
|
|
56
82
|
description: modelData.displayName || modelId
|
|
57
83
|
}));
|
|
58
84
|
|
|
59
|
-
// Warm the model validation cache
|
|
60
|
-
modelCache.validModels = new Set(modelList.map(m => m.id));
|
|
61
|
-
modelCache.lastFetched = Date.now();
|
|
62
|
-
|
|
63
85
|
return {
|
|
64
86
|
object: 'list',
|
|
65
87
|
data: modelList
|
|
@@ -14,6 +14,48 @@ import {
|
|
|
14
14
|
import { convertAnthropicToGoogle } from '../format/index.js';
|
|
15
15
|
import { deriveSessionId } from './session-manager.js';
|
|
16
16
|
|
|
17
|
+
// cloudcode-pa returns an opaque 429 RESOURCE_EXHAUSTED (reported as "quota
|
|
18
|
+
// exhausted", even when quota remains) when a request's system prompt names a
|
|
19
|
+
// known third-party AI product — e.g. an agent whose identity line says it was
|
|
20
|
+
// "created by <vendor>". This is the same class of problem as the "Antigravity"
|
|
21
|
+
// identity handling above (issue #76), but for the *caller's* identity. We
|
|
22
|
+
// neutralize those strings in the caller-supplied system parts.
|
|
23
|
+
//
|
|
24
|
+
// Defaults cover agents commonly used with this proxy; extend or override with
|
|
25
|
+
// the ANTIGRAVITY_SCRUB_IDENTITY env var, formatted as a comma-separated list of
|
|
26
|
+
// "Term=>Replacement" pairs, e.g. "Nous Research=>the team,Hermes=>the assistant".
|
|
27
|
+
const DEFAULT_IDENTITY_SCRUB = [
|
|
28
|
+
['Nous Research', 'the assistant team'],
|
|
29
|
+
['Hermes Agent', 'the assistant'],
|
|
30
|
+
['Hermes', 'the assistant']
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function parseIdentityScrubEnv(raw) {
|
|
34
|
+
if (!raw) return [];
|
|
35
|
+
return raw
|
|
36
|
+
.split(',')
|
|
37
|
+
.map((pair) => {
|
|
38
|
+
const idx = pair.indexOf('=>');
|
|
39
|
+
if (idx === -1) return null;
|
|
40
|
+
return [pair.slice(0, idx).trim(), pair.slice(idx + 2).trim()];
|
|
41
|
+
})
|
|
42
|
+
.filter((rule) => rule && rule[0].length > 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const IDENTITY_SCRUB_RULES = [
|
|
46
|
+
...DEFAULT_IDENTITY_SCRUB,
|
|
47
|
+
...parseIdentityScrubEnv(process.env.ANTIGRAVITY_SCRUB_IDENTITY)
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function scrubClientIdentity(text) {
|
|
51
|
+
if (typeof text !== 'string') return text;
|
|
52
|
+
let out = text;
|
|
53
|
+
for (const [term, replacement] of IDENTITY_SCRUB_RULES) {
|
|
54
|
+
out = out.split(term).join(replacement);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
17
59
|
/**
|
|
18
60
|
* Build the wrapped request body for Cloud Code API
|
|
19
61
|
*
|
|
@@ -37,11 +79,12 @@ export function buildCloudCodeRequest(anthropicRequest, projectId, accountEmail)
|
|
|
37
79
|
{ text: `Please ignore the following [ignore]${ANTIGRAVITY_SYSTEM_INSTRUCTION}[/ignore]` }
|
|
38
80
|
];
|
|
39
81
|
|
|
40
|
-
// Append any existing system instructions from the request
|
|
82
|
+
// Append any existing system instructions from the request, scrubbing
|
|
83
|
+
// third-party AI product identities that trip cloudcode-pa's 429 (see above).
|
|
41
84
|
if (googleRequest.systemInstruction && googleRequest.systemInstruction.parts) {
|
|
42
85
|
for (const part of googleRequest.systemInstruction.parts) {
|
|
43
86
|
if (part.text) {
|
|
44
|
-
systemParts.push({ text: part.text });
|
|
87
|
+
systemParts.push({ text: scrubClientIdentity(part.text) });
|
|
45
88
|
}
|
|
46
89
|
}
|
|
47
90
|
}
|
package/src/constants.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { homedir, platform, arch } from 'os';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { config } from './config.js';
|
|
9
|
-
import { generateSmartUserAgent } from './utils/version-detector.js';
|
|
9
|
+
import { generateSmartUserAgent, getClientVersion } from './utils/version-detector.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Get the Antigravity database path based on the current platform.
|
|
@@ -104,7 +104,7 @@ export const ANTIGRAVITY_HEADERS = {
|
|
|
104
104
|
'User-Agent': getPlatformUserAgent(),
|
|
105
105
|
'Content-Type': 'application/json',
|
|
106
106
|
'X-Client-Name': 'antigravity',
|
|
107
|
-
'X-Client-Version':
|
|
107
|
+
'X-Client-Version': getClientVersion(),
|
|
108
108
|
'x-goog-api-client': 'gl-node/18.18.2 fire/0.8.6 grpc/1.10.x' // Simulate Google Node.js client environment
|
|
109
109
|
};
|
|
110
110
|
|
|
@@ -279,16 +279,15 @@ export const ANTIGRAVITY_SYSTEM_INSTRUCTION = `You are Antigravity, a powerful a
|
|
|
279
279
|
export const MODEL_FALLBACK_MAP = {
|
|
280
280
|
'gemini-3.1-pro-high': 'claude-opus-4-6-thinking',
|
|
281
281
|
'gemini-3.1-pro-low': 'claude-sonnet-4-6',
|
|
282
|
-
'gemini-3-flash': 'claude-sonnet-4-6
|
|
282
|
+
'gemini-3-flash': 'claude-sonnet-4-6',
|
|
283
283
|
'claude-opus-4-6-thinking': 'gemini-3.1-pro-high',
|
|
284
|
-
'claude-sonnet-4-6-thinking': 'gemini-3-flash',
|
|
285
284
|
'claude-sonnet-4-6': 'gemini-3-flash'
|
|
286
285
|
};
|
|
287
286
|
|
|
288
287
|
// Default test models for each family (used by test suite)
|
|
289
288
|
export const TEST_MODELS = {
|
|
290
|
-
claude: 'claude-sonnet-4-6
|
|
291
|
-
gemini: 'gemini-3-flash'
|
|
289
|
+
claude: 'claude-sonnet-4-6',
|
|
290
|
+
gemini: 'gemini-3.8-flash-tiered'
|
|
292
291
|
};
|
|
293
292
|
|
|
294
293
|
// Default Claude CLI presets (used by WebUI settings)
|
|
@@ -300,9 +299,9 @@ export const DEFAULT_PRESETS = [
|
|
|
300
299
|
ANTHROPIC_BASE_URL: 'http://localhost:8080',
|
|
301
300
|
ANTHROPIC_MODEL: 'claude-opus-4-6-thinking',
|
|
302
301
|
ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-6-thinking',
|
|
303
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6
|
|
302
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6',
|
|
304
303
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-sonnet-4-6',
|
|
305
|
-
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-6
|
|
304
|
+
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-6',
|
|
306
305
|
ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
|
|
307
306
|
}
|
|
308
307
|
},
|
|
@@ -311,11 +310,11 @@ export const DEFAULT_PRESETS = [
|
|
|
311
310
|
config: {
|
|
312
311
|
ANTHROPIC_AUTH_TOKEN: 'test',
|
|
313
312
|
ANTHROPIC_BASE_URL: 'http://localhost:8080',
|
|
314
|
-
ANTHROPIC_MODEL: 'gemini-3.1-pro-
|
|
315
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3.1-pro-
|
|
316
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3.
|
|
317
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-3.
|
|
318
|
-
CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3.
|
|
313
|
+
ANTHROPIC_MODEL: 'gemini-3.1-pro-low',
|
|
314
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3.1-pro-low',
|
|
315
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3.5-flash-low',
|
|
316
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-3.5-flash-low',
|
|
317
|
+
CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3.5-flash-low',
|
|
319
318
|
ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
|
|
320
319
|
}
|
|
321
320
|
}
|
|
@@ -669,5 +669,42 @@ export function cleanSchema(schema) {
|
|
|
669
669
|
result.type = toGoogleType(result.type);
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
+
// Phase 6: Google's protobuf-based schema requires every ARRAY type to
|
|
673
|
+
// declare a single object `items` schema. Plain JSON Schema allows this
|
|
674
|
+
// to be omitted (`{ type: 'array' }` = "array of anything"), boolean
|
|
675
|
+
// (`items: true` = "any"), or tuple-form (`items: [...]`) - none of
|
|
676
|
+
// which the Cloud Code API accepts; it rejects the first two with
|
|
677
|
+
// "items: missing field". This runs after the items/properties
|
|
678
|
+
// recursion above (and after Phase 5's type conversion), so it applies
|
|
679
|
+
// at every nesting depth - fixing issue #368 (nested array schemas like
|
|
680
|
+
// `where.items.items` losing their innermost `items`).
|
|
681
|
+
if (result.type === 'ARRAY') {
|
|
682
|
+
if (!result.items || typeof result.items !== 'object') {
|
|
683
|
+
// Missing entirely, or a boolean schema like `items: true`.
|
|
684
|
+
result.items = { type: 'STRING' };
|
|
685
|
+
} else if (Array.isArray(result.items)) {
|
|
686
|
+
// Tuple-form `items: [...]` - Google only supports one items
|
|
687
|
+
// schema, so collapse to the most informative entry.
|
|
688
|
+
let best = null;
|
|
689
|
+
let bestScore = -1;
|
|
690
|
+
for (const option of result.items) {
|
|
691
|
+
const score = scoreSchemaOption(option);
|
|
692
|
+
if (score > bestScore) {
|
|
693
|
+
bestScore = score;
|
|
694
|
+
best = option;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
result.items = best || { type: 'STRING' };
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Phase 7: Google rejects `properties`/`required` on a non-OBJECT schema.
|
|
702
|
+
// These can survive on the current node after anyOf/oneOf flattening
|
|
703
|
+
// picked a non-object option while the parent still carried them.
|
|
704
|
+
if (result.type !== 'OBJECT') {
|
|
705
|
+
delete result.properties;
|
|
706
|
+
delete result.required;
|
|
707
|
+
}
|
|
708
|
+
|
|
672
709
|
return result;
|
|
673
710
|
}
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import { platform, homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import { existsSync } from 'fs';
|
|
4
|
+
import { existsSync, readFileSync } from 'fs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Intelligent Version Detection for Antigravity
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
*
|
|
9
|
+
* Detects versions from the local Antigravity installation's product.json.
|
|
10
|
+
* Two version values are tracked:
|
|
11
|
+
* - "version" field → X-Client-Version header (API version gate)
|
|
12
|
+
* - "ideVersion" field → User-Agent version string
|
|
13
|
+
*
|
|
14
|
+
* Detection priority (for each):
|
|
15
|
+
* 1. Environment variable override
|
|
16
|
+
* 2. product.json from local Antigravity app
|
|
17
|
+
* 3. OS-specific detection (macOS plist / Windows exe)
|
|
18
|
+
* 4. Hardcoded fallback
|
|
10
19
|
*/
|
|
11
20
|
|
|
12
|
-
// Fallback
|
|
13
|
-
const
|
|
21
|
+
// Fallback for User-Agent version (ideVersion in product.json)
|
|
22
|
+
const FALLBACK_USER_AGENT_VERSION = process.env.FALLBACK_ANTIGRAVITY_VERSION || '2.0.3';
|
|
23
|
+
|
|
24
|
+
// Fallback for X-Client-Version (top-level "version" in product.json)
|
|
25
|
+
// Can be overridden via ANTIGRAVITY_CLIENT_VERSION_FALLBACK env var
|
|
26
|
+
const FALLBACK_CLIENT_VERSION = process.env.ANTIGRAVITY_CLIENT_VERSION_FALLBACK || '1.110.0';
|
|
14
27
|
|
|
15
|
-
// Cache for the generated User-Agent string
|
|
16
28
|
let cachedUserAgent = null;
|
|
29
|
+
let cachedClientVersion = null;
|
|
30
|
+
let cachedProductJson = undefined; // undefined = not yet attempted
|
|
31
|
+
let loggedVersionInfo = false;
|
|
17
32
|
|
|
18
33
|
/**
|
|
19
34
|
* Compares two semver-ish version strings (X.Y.Z).
|
|
20
|
-
* @param {string} v1 - Version string 1
|
|
21
|
-
* @param {string} v2 - Version string 2
|
|
22
35
|
* @returns {boolean} True if v1 > v2
|
|
23
36
|
*/
|
|
24
37
|
function isVersionHigher(v1, v2) {
|
|
@@ -35,15 +48,119 @@ function isVersionHigher(v1, v2) {
|
|
|
35
48
|
}
|
|
36
49
|
|
|
37
50
|
/**
|
|
38
|
-
*
|
|
51
|
+
* Returns platform-specific search paths for product.json.
|
|
52
|
+
*/
|
|
53
|
+
function getProductJsonPaths() {
|
|
54
|
+
const os = platform();
|
|
55
|
+
const paths = [];
|
|
56
|
+
|
|
57
|
+
if (os === 'darwin') {
|
|
58
|
+
paths.push('/Applications/Antigravity.app/Contents/Resources/app/product.json');
|
|
59
|
+
paths.push(join(homedir(), 'Applications', 'Antigravity.app', 'Contents', 'Resources', 'app', 'product.json'));
|
|
60
|
+
} else if (os === 'win32') {
|
|
61
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
62
|
+
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
|
|
63
|
+
if (localAppData) {
|
|
64
|
+
paths.push(join(localAppData, 'Programs', 'Antigravity', 'resources', 'app', 'product.json'));
|
|
65
|
+
}
|
|
66
|
+
paths.push(join(programFiles, 'Antigravity', 'resources', 'app', 'product.json'));
|
|
67
|
+
} else {
|
|
68
|
+
paths.push('/usr/share/antigravity/resources/app/product.json');
|
|
69
|
+
paths.push('/opt/antigravity/resources/app/product.json');
|
|
70
|
+
paths.push('/opt/Antigravity/resources/app/product.json');
|
|
71
|
+
paths.push(join(homedir(), '.local', 'share', 'antigravity', 'resources', 'app', 'product.json'));
|
|
72
|
+
paths.push('/snap/antigravity/current/resources/app/product.json');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return paths;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find and parse product.json from the local Antigravity installation.
|
|
80
|
+
* Caches the result after first attempt.
|
|
81
|
+
* @returns {Object|null} Parsed product.json or null
|
|
82
|
+
*/
|
|
83
|
+
function getProductJson() {
|
|
84
|
+
if (cachedProductJson !== undefined) return cachedProductJson;
|
|
85
|
+
|
|
86
|
+
for (const p of getProductJsonPaths()) {
|
|
87
|
+
try {
|
|
88
|
+
if (existsSync(p)) {
|
|
89
|
+
const content = JSON.parse(readFileSync(p, 'utf8'));
|
|
90
|
+
if (content && (content.version || content.ideVersion)) {
|
|
91
|
+
cachedProductJson = content;
|
|
92
|
+
return content;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Continue to next path
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
cachedProductJson = null;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Log version detection results once at startup (lazy import to avoid circular deps).
|
|
106
|
+
*/
|
|
107
|
+
function logVersionInfo(version, source) {
|
|
108
|
+
if (loggedVersionInfo) return;
|
|
109
|
+
loggedVersionInfo = true;
|
|
110
|
+
|
|
111
|
+
import('./logger.js').then(({ logger }) => {
|
|
112
|
+
if (source === 'fallback') {
|
|
113
|
+
logger.warn(`X-Client-Version: using hardcoded fallback ${version} — product.json not found. Set ANTIGRAVITY_CLIENT_VERSION (exact version) or ANTIGRAVITY_CLIENT_VERSION_FALLBACK (fallback value) env var to override.`);
|
|
114
|
+
} else {
|
|
115
|
+
logger.debug(`X-Client-Version: ${version} (source: ${source})`);
|
|
116
|
+
}
|
|
117
|
+
}).catch(() => {});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the X-Client-Version value for API requests.
|
|
122
|
+
* Priority: ANTIGRAVITY_CLIENT_VERSION env var > product.json "version" > hardcoded fallback
|
|
123
|
+
* @returns {string} Version string (e.g. "1.110.0")
|
|
124
|
+
*/
|
|
125
|
+
export function getClientVersion() {
|
|
126
|
+
if (cachedClientVersion) return cachedClientVersion;
|
|
127
|
+
|
|
128
|
+
if (process.env.ANTIGRAVITY_CLIENT_VERSION) {
|
|
129
|
+
cachedClientVersion = process.env.ANTIGRAVITY_CLIENT_VERSION;
|
|
130
|
+
logVersionInfo(cachedClientVersion, 'env');
|
|
131
|
+
return cachedClientVersion;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const product = getProductJson();
|
|
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');
|
|
143
|
+
return cachedClientVersion;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the User-Agent version string.
|
|
148
|
+
* Priority: FALLBACK_ANTIGRAVITY_VERSION env var > product.json "ideVersion" > OS detection > fallback
|
|
39
149
|
* @returns {{ version: string, source: string }}
|
|
40
150
|
*/
|
|
41
|
-
function
|
|
151
|
+
function getUserAgentVersionConfig() {
|
|
152
|
+
if (process.env.FALLBACK_ANTIGRAVITY_VERSION) {
|
|
153
|
+
return { version: process.env.FALLBACK_ANTIGRAVITY_VERSION, source: 'env' };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const product = getProductJson();
|
|
157
|
+
if (product?.ideVersion && isVersionHigher(product.ideVersion, FALLBACK_USER_AGENT_VERSION)) {
|
|
158
|
+
return { version: product.ideVersion, source: 'product.json' };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// OS-specific detection (legacy — reads app binary metadata directly)
|
|
42
162
|
const os = platform();
|
|
43
163
|
let detectedVersion = null;
|
|
44
|
-
let finalVersion = FALLBACK_ANTIGRAVITY_VERSION;
|
|
45
|
-
let source = 'fallback';
|
|
46
|
-
|
|
47
164
|
try {
|
|
48
165
|
if (os === 'darwin') {
|
|
49
166
|
detectedVersion = getVersionMacos();
|
|
@@ -54,16 +171,11 @@ function getVersionConfig() {
|
|
|
54
171
|
// Silently fail and use fallback
|
|
55
172
|
}
|
|
56
173
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
finalVersion = detectedVersion;
|
|
60
|
-
source = 'local';
|
|
174
|
+
if (detectedVersion && isVersionHigher(detectedVersion, FALLBACK_USER_AGENT_VERSION)) {
|
|
175
|
+
return { version: detectedVersion, source: 'local' };
|
|
61
176
|
}
|
|
62
177
|
|
|
63
|
-
return {
|
|
64
|
-
version: finalVersion,
|
|
65
|
-
source
|
|
66
|
-
};
|
|
178
|
+
return { version: FALLBACK_USER_AGENT_VERSION, source: 'fallback' };
|
|
67
179
|
}
|
|
68
180
|
|
|
69
181
|
/**
|
|
@@ -74,11 +186,10 @@ function getVersionConfig() {
|
|
|
74
186
|
export function generateSmartUserAgent() {
|
|
75
187
|
if (cachedUserAgent) return cachedUserAgent;
|
|
76
188
|
|
|
77
|
-
const { version } =
|
|
189
|
+
const { version } = getUserAgentVersionConfig();
|
|
78
190
|
const os = platform();
|
|
79
191
|
const architecture = process.arch;
|
|
80
192
|
|
|
81
|
-
// Map Node.js platform names to binary-friendly names
|
|
82
193
|
const osName = os === 'darwin' ? 'darwin' : (os === 'win32' ? 'win32' : 'linux');
|
|
83
194
|
|
|
84
195
|
cachedUserAgent = `antigravity/${version} ${osName}/${architecture}`;
|
|
@@ -131,4 +242,3 @@ function getVersionWindows() {
|
|
|
131
242
|
}
|
|
132
243
|
return null;
|
|
133
244
|
}
|
|
134
|
-
|