AG X v2.0.3 - Antigravity fork with multi-provider support

Features:
- Welcome screen on first run (provider choice before LS starts)
- 15+ AI providers (Google Gemini, OpenAI, Anthropic, DeepSeek, Ollama, etc.)
- Provider config syncs to endpoints.json for translation proxy
- Built-in Node.js translation proxy for non-native backends
- Auto-update support, tray integration, URI scheme handler
This commit is contained in:
admin
2026-05-22 23:20:10 +04:00
Unverified
commit 43e2a2f78f
46 changed files with 7719 additions and 0 deletions

509
dist/languageServer.js vendored Normal file
View File

@@ -0,0 +1,509 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LS_BINARY = void 0;
exports.getLsCL = getLsCL;
exports.getLsProcess = getLsProcess;
exports.getLsPort = getLsPort;
exports.clearLsProcess = clearLsProcess;
exports.extractCrashStackTrace = extractCrashStackTrace;
exports.startLanguageServer = startLanguageServer;
exports.setIntentionalTermination = setIntentionalTermination;
exports.startAndMonitorLanguageServer = startAndMonitorLanguageServer;
exports.killLanguageServer = killLanguageServer;
exports.setupLocalCertTrust = setupLocalCertTrust;
const child_process_1 = require("child_process");
const electron_1 = require("electron");
const shell_env_1 = require("shell-env");
const fs = __importStar(require("fs"));
const path_1 = __importDefault(require("path"));
const readline = __importStar(require("readline"));
const stream_1 = require("stream");
const paths_1 = require("./paths");
const constants_1 = require("./constants");
const utils_1 = require("./utils");
// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
const LS_STARTUP_TIMEOUT_MS = 60000;
// ---------------------------------------------------------------------------
// Crash Monitoring Constants
// ---------------------------------------------------------------------------
const RESTART_WINDOW_MS = 60000;
const MAX_RESTARTS = 3;
const RESTART_COOLDOWN_MS = 2000;
const MAX_STDERR_BUFFER = 100000;
const CRASH_TRIGGER_PHRASES = [
'panic:',
'fatal error:',
'unexpected fault address',
'runtime:',
'running GoogleExitFunction',
'panic serving',
];
const isWindows = process.platform === 'win32';
const binName = isWindows ? 'language_server.exe' : 'language_server';
exports.LS_BINARY = electron_1.app.isPackaged
? path_1.default.join(process.resourcesPath, 'bin', binName)
: process.env.CODEIUM_LANGUAGE_SERVER_BIN ||
path_1.default.join(__dirname, '..', 'bin', binName);
/**
* Gets the build CL of the language server by running it with --stamp.
*/
function getLsCL() {
return new Promise((resolve) => {
(0, child_process_1.execFile)(exports.LS_BINARY, ['--stamp'], (error, stdout, _stderr) => {
if (error) {
console.error('Failed to get LS stamp:', error);
resolve('');
return;
}
const match = /Built at CL: (\d+)/.exec(stdout);
if (match) {
resolve(match[1]);
}
else {
resolve('');
}
});
});
}
// Pattern: "listening on <proto> port at <N> for HTTP or HTTPS"
const PORT_PATTERN = /listening on \w+ port at (\d+) for HTTP(S)?\b/i;
// Pattern: OAuth authorization URL
const AUTH_URL_PATTERN = /https:\/\/accounts\.google\.com\/o\/oauth2\/auth\S+/;
// ---------------------------------------------------------------------------
// State
// ---------------------------------------------------------------------------
let _lsProcess = null;
let _proxyProcess = null;
let _lsPort = 0;
let _intentionalTermination = false;
let _restartCount = 0;
let _lastRestartTime = 0;
/** Returns the active language server process, or null if not running. */
function getLsProcess() {
return _lsProcess;
}
/** Returns the active language server port, or 0 if not running. */
function getLsPort() {
return _lsPort;
}
/** Clears the language server process reference (call after killing it). */
function clearLsProcess() {
_lsProcess = null;
}
// ---------------------------------------------------------------------------
// Crash log extraction
// ---------------------------------------------------------------------------
/**
* Extract lines after a crash trigger phrase from a list of stderr lines.
* Returns all lines from the first trigger phrase onwards.
*/
function getLinesAfterCrash(lines) {
const crashLines = [];
let foundTrigger = false;
for (const line of lines) {
if (CRASH_TRIGGER_PHRASES.some((phrase) => line.includes(phrase))) {
foundTrigger = true;
}
if (foundTrigger) {
crashLines.push(line);
}
}
return crashLines;
}
/**
* Best-effort extraction of the crash stack trace from buffered stderr.
* Returns the stack trace string, or undefined if no trigger phrase was found.
*/
function extractCrashStackTrace(stderr) {
const lines = stderr.split('\n');
const crashLines = getLinesAfterCrash(lines);
return crashLines.length > 0 ? crashLines.join('\n') : undefined;
}
/**
* Sets environment variables for bundled node modules so the language
* server can find them.
*
* NOTE: If you add a new module that needs to be executed this way:
* 1. Add it to `asarUnpack` in `package.json` so it is available on the filesystem.
* 2. Add it to `modules` in the callsite of setupNodeModules.
*/
function setupNodeModules(env, modules) {
for (const mod of modules) {
let entryPoint = '';
if (!electron_1.app.isPackaged) {
entryPoint = path_1.default.join(__dirname, '..', 'node_modules', mod.name, ...mod.relativePath);
}
else {
entryPoint = path_1.default.join(process.resourcesPath, 'app.asar.unpacked', 'node_modules', mod.name, ...mod.relativePath);
}
env[mod.envVar] = entryPoint;
}
}
/**
* Spawn the language server and resolve with a LanguageServerHandle once
* the LS reports its HTTP port. Rejects on timeout or unexpected exit
* during startup.
*
* After resolving, callers should monitor `handle.exitPromise` to detect
* crashes that occur after startup.
*/
const os = require('os');
function ensureProxyStarted() {
return new Promise((resolve) => {
if (_proxyProcess) {
resolve();
return;
}
try {
const home = os.homedir();
const endpointsPath = path_1.default.join(home, '.codex', 'endpoints.json');
const activePath = path_1.default.join(home, '.codex', '.active-endpoint.json');
const proxyConfigDir = path_1.default.join(home, '.cache', 'codex-proxy');
const activeConfigPath = path_1.default.join(proxyConfigDir, 'proxy-active.json');
if (!fs.existsSync(proxyConfigDir)) {
fs.mkdirSync(proxyConfigDir, { recursive: true });
}
let endpointsData = { default: "", endpoints: [] };
if (fs.existsSync(endpointsPath)) {
endpointsData = JSON.parse(fs.readFileSync(endpointsPath, 'utf8'));
}
let activeName = "";
if (fs.existsSync(activePath)) {
try {
const activeData = JSON.parse(fs.readFileSync(activePath, 'utf8'));
activeName = activeData.active;
} catch (e) {}
}
if (!activeName) {
activeName = endpointsData.default || (endpointsData.endpoints[0] && endpointsData.endpoints[0].name) || "";
}
const endpoint = endpointsData.endpoints.find(e => e.name === activeName) || endpointsData.endpoints[0];
if (!endpoint) {
console.log('[Codex] No active endpoint configured in ~/.codex/endpoints.json');
resolve();
return;
}
const modelList = endpoint.models || [];
const pcfg = {
port: 48080,
backend_type: endpoint.backend_type,
target_url: endpoint.base_url,
api_key: endpoint.api_key,
cc_version: endpoint.cc_version || "",
oauth_provider: endpoint.oauth_provider || "",
reasoning_enabled: endpoint.reasoning_enabled !== undefined ? endpoint.reasoning_enabled : true,
reasoning_effort: endpoint.reasoning_effort || "medium",
models: modelList.map(m => ({ id: m, object: "model", created: 1700000000, owned_by: endpoint.name }))
};
fs.writeFileSync(activeConfigPath, JSON.stringify(pcfg, null, 2), 'utf8');
console.log('[AG X] Starting built-in Node.js translation proxy for:', endpoint.name);
// Use Electron's built-in Node.js to run our translation proxy
const proxyScript = path_1.default.join(__dirname, 'services', 'translationProxy.js');
const nodeBin = process.execPath;
_proxyProcess = (0, child_process_1.spawn)(nodeBin, [proxyScript], {
stdio: 'ignore',
detached: true,
env: { ...process.env }
});
_proxyProcess.unref();
setTimeout(() => {
resolve();
}, 500);
} catch (err) {
console.error('[AG X] Failed to auto-start translation proxy:', err);
resolve();
}
});
}
function startLanguageServer(port, csrf, headless) {
return new Promise(async (resolve, reject) => {
await ensureProxyStarted();
process.env.ANTIGRAVITY_API_SERVER_URL = 'http://127.0.0.1:48080';
process.env.ANTIGRAVITY_CLOUD_CODE_ENDPOINT = 'http://127.0.0.1:48080';
const logStream = fs.createWriteStream((0, paths_1.getLsLogPath)(), { flags: 'w' });
// We need to pass the override flags because the LS is running in standalone mode
const args = [
'--standalone',
'--override_ide_name',
'ag-x',
'--subclient_type',
'hub',
'--override_ide_version',
electron_1.app.getVersion(),
'--override_user_agent_name',
'ag-x',
'--https_server_port',
String(port),
'--csrf_token',
csrf,
'--app_data_dir',
(0, paths_1.getAppDataDirName)(),
'--api_server_url',
process.env.ANTIGRAVITY_API_SERVER_URL || 'https://generativelanguage.googleapis.com',
'--cloud_code_endpoint',
process.env.ANTIGRAVITY_CLOUD_CODE_ENDPOINT || 'https://daily-cloudcode-pa.googleapis.com',
'--enable_sidecars',
];
if (headless) {
args.push('--headless');
}
console.log(`\nSpawning: ${exports.LS_BINARY} ${args.join(' ')}\n`);
// Electron apps don't inherit shell environment variables when they are not launched through the terminal.
// We need to load the shell env explicitly so the language server can discover tools in the user's environment.
const env = { ...process.env, ...(0, shell_env_1.shellEnvSync)() };
// We don't read the file to avoid adding start up latency.
// LS will read when browser recording encoder is invoked.
env['AGY_BROWSER_ACTIVE_PORT_FILE'] = (0, paths_1.getActivePortFilePath)();
(0, utils_1.setupNodeWrapper)(env);
setupNodeModules(env, [
{
name: 'chrome-devtools-mcp',
envVar: 'CHROME_DEVTOOLS_MCP_JS',
relativePath: ['build', 'src', 'bin', 'chrome-devtools-mcp.js'],
},
]);
_lsProcess = (0, child_process_1.spawn)(exports.LS_BINARY, args, {
stdio: ['pipe', 'pipe', 'pipe'],
env,
});
if (!headless) {
// Close stdin immediately — the LS may block waiting for metadata on stdin.
_lsProcess.stdin.end();
}
const combined = new stream_1.PassThrough();
_lsProcess.stdout.pipe(combined, { end: false });
_lsProcess.stderr.pipe(combined, { end: false });
// Buffer stderr for crash log extraction (ring buffer)
const stderrChunks = [];
let stderrLength = 0;
_lsProcess.stderr.on('data', (data) => {
const str = data.toString();
stderrChunks.push(str);
stderrLength += str.length;
while (stderrChunks.length > 0 && stderrLength > MAX_STDERR_BUFFER) {
stderrLength -= stderrChunks.shift().length;
}
});
let resolved = false;
let logStreamEnded = false;
const timer = setTimeout(() => {
if (!resolved) {
resolved = true;
reject(new Error(`Timeout: language server did not report its port within ${LS_STARTUP_TIMEOUT_MS / 1000}s`));
}
}, LS_STARTUP_TIMEOUT_MS);
const rl = readline.createInterface({ input: combined, crlfDelay: Infinity });
rl.on('close', () => {
if (!logStreamEnded) {
logStreamEnded = true;
logStream.end();
}
});
rl.on('line', (line) => {
if (!logStreamEnded) {
logStream.write(line + '\n');
}
if (!resolved) {
const m = PORT_PATTERN.exec(line);
if (m) {
resolved = true;
clearTimeout(timer);
const actualPort = parseInt(m[1], 10);
_lsPort = actualPort;
resolve({
port: actualPort,
process: _lsProcess,
exitPromise,
});
}
}
const authMatch = AUTH_URL_PATTERN.exec(line);
if (authMatch) {
console.log('\n' + '='.repeat(60));
console.log(' Please visit the following URL to authorize.');
console.log(' After authorizing, paste the authorization code below.');
console.log(` ${authMatch[0]}`);
console.log('='.repeat(60) + '\n');
}
});
// Exit promise — resolves whenever the process exits (whether during
// startup or after). Includes crash stack trace extraction.
const exitPromise = new Promise((exitResolve) => {
_lsProcess.on('exit', (code, signal) => {
if (!logStreamEnded) {
logStreamEnded = true;
logStream.end();
}
const fullStderr = stderrChunks.join('');
const crashStackTrace = extractCrashStackTrace(fullStderr);
// If we haven't resolved the startup promise yet, reject it.
if (!resolved) {
resolved = true;
clearTimeout(timer);
reject(new Error(`Language server exited unexpectedly (code=${code}, signal=${signal})`));
}
exitResolve({ code, signal, crashStackTrace });
});
});
});
}
/** Sets whether the termination was intentional (suppresses crash reports). */
function setIntentionalTermination(value) {
_intentionalTermination = value;
}
/**
* Start the language server AND set up the restart monitoring loop.
* Resolves with the handle on first successful startup.
*/
async function startAndMonitorLanguageServer(port, csrf, options = {}) {
setIntentionalTermination(false); // Reset
const handle = await startLanguageServer(port, csrf, options.headless);
_lsPort = handle.port;
if (options.onPortChanged) {
options.onPortChanged(_lsPort);
}
monitorLsCrashInternal(handle, port, csrf, options);
return handle;
}
function monitorLsCrashInternal(handle, port, csrf, options) {
void handle.exitPromise.then(async (exitInfo) => {
clearLsProcess();
if (_intentionalTermination) {
return;
}
const { code, signal, crashStackTrace } = exitInfo;
const summary = signal
? `killed by signal ${signal}`
: `exited with code ${code}`;
console.error(`\nLanguage server crashed: ${summary}`);
if (crashStackTrace) {
console.error('--- Crash Stack Trace ---');
console.error(crashStackTrace);
console.error('--- End Crash Stack trace ---');
}
const now = Date.now();
if (now - _lastRestartTime > RESTART_WINDOW_MS) {
_restartCount = 0;
}
_lastRestartTime = now;
if (_restartCount >= MAX_RESTARTS) {
const msg = `Language server crashed ${MAX_RESTARTS} times in a row. Giving up.`;
console.error(msg);
return;
}
_restartCount++;
console.log(`Attempting restart ${_restartCount}/${MAX_RESTARTS} in ${RESTART_COOLDOWN_MS / 1000}s...`);
await sleep(RESTART_COOLDOWN_MS);
if (_intentionalTermination) {
return;
}
try {
const newHandle = await startLanguageServer(port, csrf);
_lsPort = newHandle.port;
if (options.onPortChanged) {
options.onPortChanged(_lsPort);
}
// Recurse
monitorLsCrashInternal(newHandle, port, csrf, options);
}
catch (err) {
console.error(`Failed to restart language server: ${err.message}`);
}
});
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function killLanguageServer() {
setIntentionalTermination(true);
const proc = getLsProcess();
if (proc) {
const pid = proc.pid;
console.log('Shutting down language server…');
const exitPromise = new Promise((resolve) => {
proc.once('exit', () => {
resolve();
});
});
proc.kill('SIGTERM');
const result = await Promise.race([
exitPromise.then(() => 'exited'),
new Promise((resolve) => setTimeout(() => resolve('timeout'), 5000)),
]);
if (result === 'timeout' && pid !== undefined) {
console.warn(`Language server (PID ${pid}) did not exit gracefully within 5s. Sending SIGKILL.`);
try {
process.kill(pid, 'SIGKILL');
}
catch {
// Process already dead or exited
}
}
clearLsProcess();
}
if (_proxyProcess) {
console.log('[Codex] Stopping background translation proxy…');
try {
_proxyProcess.kill('SIGTERM');
}
catch (e) {}
_proxyProcess = null;
}
}
/**
* Sets up certificate verification in Electron to trust the local self-signed cert
* used by the language server. It verifies that the certificate fingerprint matches
* the hardcoded `LS_CERT_FINGERPRINT`.
*
* TODO: Generate the cert.pem file dynamically
*/
function setupLocalCertTrust() {
electron_1.session.defaultSession.setCertificateVerifyProc((request, callback) => {
if ((request.hostname === '127.0.0.1' || request.hostname === 'localhost') &&
request.certificate.fingerprint === constants_1.LS_CERT_FINGERPRINT) {
callback(0); // Accept
}
else {
callback(-3); // Default validation
}
});
}