| |
| import './dashboard/logger.js'; |
| import { initAuth, isAuthenticated, saveAccountsSync } from './auth.js'; |
| import { startLanguageServer, waitForReady, isLanguageServerRunning, stopLanguageServer, cleanupOrphanLanguageServers } from './langserver.js'; |
| import { startServer } from './server.js'; |
| import { config, log } from './config.js'; |
| import { existsSync, mkdirSync, readdirSync, rmSync } from 'fs'; |
| import { tmpdir } from 'os'; |
| import { execSync } from 'child_process'; |
| import { dirname, join } from 'path'; |
| import { fileURLToPath } from 'url'; |
| import { VERSION, BRAND } from './version.js'; |
| import { abortActiveSse } from './sse-registry.js'; |
| import { startQuietWindowAutoUpdate, stopQuietWindowAutoUpdate } from './dashboard/quiet-window-updater.js'; |
| export { VERSION, BRAND }; |
|
|
| function workspaceBase() { |
| const tmpDir = process.env.TEMP || process.env.TMP || tmpdir(); |
| const suffix = process.env.HOSTNAME ? `-${process.env.HOSTNAME}` : ''; |
| return join(tmpDir, `windsurf-workspace${suffix}`); |
| } |
|
|
| function resetWorkspace() { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const wsBase = workspaceBase(); |
| try { |
| mkdirSync(wsBase, { recursive: true }); |
| for (const name of readdirSync(wsBase)) { |
| try { rmSync(join(wsBase, name), { recursive: true, force: true }); } catch {} |
| } |
| } catch {} |
| try { |
| mkdirSync(join('/opt/windsurf/data', 'db'), { recursive: true }); |
| } catch {} |
| } |
|
|
| async function main() { |
| const banner = ` |
| _ _ _ _ __ _ ____ ___ |
| | | | (_) | | / _| / \\ | _ \\_ _| |
| | | | |_ _ __ __| |___ _ _ _ __ _| |_ / _ \\ | |_) | | |
| | |/\\| | | '_ \\ / _\` / __| | | | '__|_ _|/ ___ \\| __/| | |
| \\ /\\ / | | | | (_| \\__ \\ |_| | | |_| /_/ \\_\\_| |___| |
| \\/ \\/|_|_| |_|\\__,_|___/\\__,_|_| |
| ${BRAND} v${VERSION} |
| `; |
| console.log(banner); |
| console.log(` OpenAI-compatible proxy for Windsurf β by dwgx1337\n`); |
|
|
| |
| |
| |
| |
| const binaryPath = config.lsBinaryPath; |
| if (!existsSync(binaryPath) && process.platform === 'win32') { |
| log.warn('Windows detected: the Language Server binary is Linux/macOS only.'); |
| log.warn('Options: (1) Use Docker (see docker-compose.yml), (2) Use WSL2, or'); |
| log.warn('(3) Point LS_BINARY_PATH to a Windsurf desktop app language_server binary.'); |
| } |
| if (!existsSync(binaryPath) && process.platform !== 'win32') { |
| const scriptPath = (() => { |
| try { |
| const here = dirname(fileURLToPath(import.meta.url)); |
| return join(here, '..', 'install-ls.sh'); |
| } catch { return null; } |
| })(); |
| if (scriptPath && existsSync(scriptPath)) { |
| log.info(`Language server binary missing at ${binaryPath}`); |
| log.info(`Auto-installing via ${scriptPath} β this runs once.`); |
| try { |
| execSync(`bash "${scriptPath}"`, { |
| stdio: 'inherit', |
| env: { ...process.env, LS_INSTALL_PATH: binaryPath }, |
| }); |
| log.info('Language server binary installed.'); |
| } catch (err) { |
| log.error(`Auto-install failed: ${err.message}`); |
| log.error('Run manually: bash install-ls.sh (or set LS_BINARY_PATH to point at an existing binary)'); |
| } |
| } |
| } |
|
|
| if (existsSync(binaryPath)) { |
| resetWorkspace(); |
|
|
| |
| |
| |
| |
| |
| |
| if (process.env.WINDSURFAPI_SKIP_LS_CLEANUP !== '1') { |
| try { |
| const r = cleanupOrphanLanguageServers(); |
| if (r.killed > 0) log.info(`LS cleanup: scanned ${r.scanned} candidate(s), killed ${r.killed} orphan(s)`); |
| } catch (e) { log.warn(`LS cleanup error (non-fatal): ${e.message}`); } |
| } |
|
|
| await startLanguageServer({ |
| binaryPath, |
| port: config.lsPort, |
| apiServerUrl: config.codeiumApiUrl, |
| }); |
|
|
| try { |
| await waitForReady(30000); |
| |
| |
| log.info('LS ready β fetching model catalog'); |
| } catch (err) { |
| log.error(`Language server failed to start: ${err.message}`); |
| log.error('Chat completions will not work without the language server.'); |
| log.error('Run: bash install-ls.sh (now uses Windsurf desktop LS, not stale Exafunction)'); |
| } |
| } else { |
| log.warn(`Language server binary not found at ${binaryPath}`); |
| log.warn('Install it with: download Windsurf Linux tarball and extract language_server_linux_x64'); |
| } |
|
|
| |
| await initAuth(); |
|
|
| if (!isAuthenticated()) { |
| log.warn('No accounts configured. Add via:'); |
| log.warn(' POST /auth/login {"token":"..."}'); |
| log.warn(' POST /auth/login {"api_key":"..."}'); |
| } |
|
|
| const server = startServer(); |
|
|
| |
| |
| |
| |
| try { startQuietWindowAutoUpdate(); } catch (e) { log.warn(`quiet-window: failed to start: ${e.message}`); } |
|
|
| let shuttingDown = false; |
| const shutdown = (signal) => { |
| if (shuttingDown) return; |
| shuttingDown = true; |
| const inflight = server.getActiveRequests?.() ?? '?'; |
| log.info(`${signal} received β draining ${inflight} in-flight requests (up to 30s)...`); |
| const abortedSse = abortActiveSse('server shutting down'); |
| if (abortedSse) log.warn(`Aborted ${abortedSse} active SSE stream(s): server shutting down`); |
| if (typeof server.closeIdleConnections === 'function') server.closeIdleConnections(); |
| server.close(() => { |
| log.info('HTTP server closed, flushing state + stopping language server'); |
| |
| |
| |
| try { saveAccountsSync(); } catch {} |
| try { stopQuietWindowAutoUpdate(); } catch {} |
| try { stopLanguageServer(); } catch {} |
| process.exit(0); |
| }); |
| setTimeout(() => { |
| log.warn('Drain timeout, forcing exit'); |
| try { saveAccountsSync(); } catch {} |
| try { stopQuietWindowAutoUpdate(); } catch {} |
| try { stopLanguageServer(); } catch {} |
| process.exit(0); |
| }, 30_000); |
| }; |
| process.on('SIGINT', () => shutdown('SIGINT')); |
| process.on('SIGTERM', () => shutdown('SIGTERM')); |
| } |
|
|
| main().catch(err => { console.error('Fatal:', err); process.exit(1); }); |
|
|