W
File size: 7,798 Bytes
2b64d42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Logger must be imported first to patch log functions before other modules use them
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() {
  // Wipe the workspace on every startup. If we don't, files created by
  // previous chat sessions (e.g. Claude "editing" config.yaml/lru_cache.py
  // via the baked-in Cascade tool prompts) persist and pollute the next
  // request β€” the model sees them at session init and starts narrating
  // edits to files the caller never mentioned.
  //
  // Using Node fs APIs instead of an `execSync('mkdir -p ... && rm -rf')`
  // shell pipeline so this is correct on Windows, macOS, and Linux without
  // depending on a POSIX shell.
  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`);

  // Start language server binary.
  // Auto-install if missing β€” users repeatedly miss the manual install step
  // and open "request crashes" issues (see #18), so we just do it ourselves.
  // Skipped on Windows (LS is Linux-only) and when install-ls.sh isn't present.
  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();

    // v2.0.85 (#127 123cek): kill any leftover language_server_linux_x64
    // processes from prior runs (PM2 SIGKILL / dashboard self-update via
    // process.exit() / earlier crash) before we start ours. Otherwise
    // they keep their LS pool ports occupied and accumulate over self-
    // update cycles. Setting WINDSURFAPI_SKIP_LS_CLEANUP=1 disables
    // (e.g. multi-WindsurfAPI on a shared host).
    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);
      // v2.0.93: if default LS started but proxy-LS crashed, give the
      // manage child (port 42101) a moment to restart before syncing models.
      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');
  }

  // Init auth pool
  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();

  // v2.0.67 (#112) β€” quiet-window auto-update watcher. No-op until the
  // operator flips experimental.autoUpdateQuietWindow on (default off).
  // Runs alongside the HTTP server; ticks every minute, polls the request
  // ring + cooldown gates, fires runDockerSelfUpdate during real lulls.
  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');
      // Persist any in-memory account updates (capability probes, error
      // counts, rate-limit cooldowns) before PM2 restarts us. Debounced
      // saves would otherwise be killed by the exit below.
      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); });