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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antigravity-claude-proxy",
3
- "version": "2.8.6",
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",
@@ -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
- x-html="Redact.logMessage(log.message).replace(/\n/g, '<br>')"></span>
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
- return 'unknown';
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
- sanitized.enum = [value];
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. product.json from local Antigravity app
17
- * 3. OS-specific detection (macOS plist / Windows exe)
18
- * 4. Hardcoded fallback
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
- const FALLBACK_USER_AGENT_VERSION = process.env.FALLBACK_ANTIGRAVITY_VERSION || '2.0.3';
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 ANTIGRAVITY_CLIENT_VERSION (exact version) or ANTIGRAVITY_CLIENT_VERSION_FALLBACK (fallback value) env var to override.`);
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 > product.json "version" > hardcoded fallback
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
- 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');
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 > product.json "ideVersion" > OS detection > fallback
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
- const product = getProductJson();
157
- if (product?.ideVersion && isVersionHigher(product.ideVersion, FALLBACK_USER_AGENT_VERSION)) {
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 detectedVersion = null;
183
+ let localVersion = null;
164
184
  try {
165
185
  if (os === 'darwin') {
166
- detectedVersion = getVersionMacos();
186
+ localVersion = getVersionMacos();
167
187
  } else if (os === 'win32') {
168
- detectedVersion = getVersionWindows();
188
+ localVersion = getVersionWindows();
169
189
  }
170
190
  } catch (error) {
171
191
  // Silently fail and use fallback
172
192
  }
173
193
 
174
- if (detectedVersion && isVersionHigher(detectedVersion, FALLBACK_USER_AGENT_VERSION)) {
175
- return { version: detectedVersion, source: 'local' };
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