Critical fixes: - Translation proxy now uses system Node.js (not Electron binary) - Removed duplicate proxy start causing port conflicts - Added port availability check before spawning proxy - Fixed welcome:choice double resolve() - Fixed settings.html close using deprecated remote - Fixed translationProxy /v1 for openai-compat backends - Proxy no longer detached/unref - properly tracked as child - SingletonLock cleanup on startup Verified E2E: - Welcome screen on first run ✓ - Provider selection works ✓ - Settings save + sync ✓ - Translation proxy starts correctly ✓ - LS connects to proxy ✓ - --ag-reset works ✓
120 lines
4.6 KiB
JavaScript
120 lines
4.6 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.ApiProxy = void 0;
|
|
const http = require("http");
|
|
const https = require("https");
|
|
const child_process = require("child_process");
|
|
const path = require("path");
|
|
const os = require("os");
|
|
const fs = require("fs");
|
|
const translationProxy_1 = require("./translationProxy");
|
|
|
|
/**
|
|
* API Proxy for AG X.
|
|
*
|
|
* All backends now use the built-in Node.js translation proxy.
|
|
* No external Python or tools required.
|
|
*/
|
|
class ApiProxy {
|
|
constructor(providerService) {
|
|
this.providerService = providerService;
|
|
this.server = null;
|
|
this.proxyProcess = null;
|
|
this.port = providerService.getProxyPort();
|
|
}
|
|
start() {
|
|
if (this.server || this.proxyProcess) {
|
|
return;
|
|
}
|
|
const backendType = this.providerService.getActiveBackendType();
|
|
if (backendType === 'gemini-native' || backendType === 'native') {
|
|
console.log('[ApiProxy] Native backend, no proxy needed');
|
|
return;
|
|
}
|
|
this.startInternalProxy();
|
|
}
|
|
/**
|
|
* Start the built-in Node.js translation proxy as a child process
|
|
*/
|
|
startInternalProxy() {
|
|
const activeProvider = this.providerService.getActiveProvider();
|
|
const config = this.providerService.getProviderConfig(activeProvider);
|
|
if (!config) {
|
|
console.error('[ApiProxy] No provider config found');
|
|
return;
|
|
}
|
|
// Prepare the proxy config
|
|
const configDir = path.join(os.homedir(), '.cache', 'ag-x-proxy');
|
|
if (!fs.existsSync(configDir)) {
|
|
fs.mkdirSync(configDir, { recursive: true });
|
|
}
|
|
const models = (config.models || []).map((m) => {
|
|
if (typeof m === 'string') {
|
|
return { id: m, object: "model", created: 1700000000, owned_by: activeProvider };
|
|
}
|
|
return m;
|
|
});
|
|
const proxyConfig = {
|
|
port: this.port,
|
|
backend_type: config.backendType || 'openai-compat',
|
|
target_url: config.apiUrl || '',
|
|
api_key: config.apiKey || '',
|
|
cc_version: config.ccVersion || '0.26.8',
|
|
oauth_provider: config.oauthProvider || '',
|
|
reasoning_enabled: config.reasoningEnabled !== undefined ? config.reasoningEnabled : true,
|
|
reasoning_effort: config.reasoningEffort || 'medium',
|
|
models: models,
|
|
};
|
|
const configPath = path.join(configDir, `ag-x-proxy-${this.port}.json`);
|
|
fs.writeFileSync(configPath, JSON.stringify(proxyConfig, null, 2), 'utf-8');
|
|
console.log(`[ApiProxy] Starting built-in Node.js translation proxy on port ${this.port}`);
|
|
// Use Electron's Node.js binary to run our translation proxy
|
|
const proxyScript = path.join(__dirname, 'translationProxy.js');
|
|
// Use system node (not Electron's process.execPath which starts a GUI)
|
|
const nodeBin = process.execPath.includes('electron') || process.execPath.includes('AG-X')
|
|
? child_process.execSync('which node 2>/dev/null || echo /usr/bin/node').toString().trim()
|
|
: process.execPath;
|
|
this.proxyProcess = child_process.spawn(nodeBin, [proxyScript], {
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
detached: false,
|
|
env: { ...process.env },
|
|
});
|
|
this.proxyProcess.stdout?.on('data', (data) => {
|
|
const lines = data.toString().trim().split('\n');
|
|
for (const line of lines) {
|
|
console.log(`[Proxy] ${line}`);
|
|
}
|
|
});
|
|
this.proxyProcess.stderr?.on('data', (data) => {
|
|
const lines = data.toString().trim().split('\n');
|
|
for (const line of lines) {
|
|
console.log(`[Proxy] ${line}`);
|
|
}
|
|
});
|
|
this.proxyProcess.on('error', (err) => {
|
|
console.error('[ApiProxy] Proxy error:', err);
|
|
});
|
|
this.proxyProcess.on('exit', (code, signal) => {
|
|
console.log(`[ApiProxy] Proxy exited with code ${code}, signal ${signal}`);
|
|
this.proxyProcess = null;
|
|
});
|
|
this.proxyProcess.unref();
|
|
console.log(`[ApiProxy] Built-in Node.js proxy spawned for ${config.backendType} on port ${this.port}`);
|
|
}
|
|
stop() {
|
|
if (this.proxyProcess) {
|
|
try {
|
|
this.proxyProcess.kill();
|
|
}
|
|
catch (e) { /* ignore */ }
|
|
this.proxyProcess = null;
|
|
}
|
|
if (this.server) {
|
|
this.server.close();
|
|
this.server = null;
|
|
console.log('[ApiProxy] Stopped');
|
|
}
|
|
}
|
|
}
|
|
exports.ApiProxy = ApiProxy;
|