()
| 34 | } |
| 35 | |
| 36 | private async _start(): Promise<void> { |
| 37 | if (this.process) { |
| 38 | await this.waitUntilReady() |
| 39 | return |
| 40 | } |
| 41 | |
| 42 | const pythonExecutable = this.resolvePythonExecutable() |
| 43 | const apiDir = this.resolveApiDir() |
| 44 | |
| 45 | console.log('[PythonBridge] Starting FastAPI at', apiDir) |
| 46 | console.log('[PythonBridge] Python executable:', pythonExecutable) |
| 47 | |
| 48 | await this.killProcessOnPort() |
| 49 | |
| 50 | this.process = spawn(pythonExecutable, ['-m', 'uvicorn', 'main:app', '--host', API_HOST, '--port', String(API_PORT)], { |
| 51 | cwd: apiDir, |
| 52 | env: { |
| 53 | ...cleanPythonEnv(), |
| 54 | PYTHONUNBUFFERED: '1', |
| 55 | // No PYTHONPATH needed - the venv's Python has its own isolated site-packages |
| 56 | MODELS_DIR: this.resolveModelsDir(), |
| 57 | WORKSPACE_DIR: this.resolveWorkspaceDir(), |
| 58 | EXTENSIONS_DIR: this.resolveExtensionsDir(), |
| 59 | SELECTED_MODEL_ID: process.env['SELECTED_MODEL_ID'] ?? '', |
| 60 | HUGGING_FACE_HUB_TOKEN: this.resolveHfToken(), |
| 61 | HF_TOKEN: this.resolveHfToken(), |
| 62 | }, |
| 63 | // On Unix, put the bridge in its own process group so every subprocess |
| 64 | // it spawns (extension runners, etc.) inherits that group. On shutdown |
| 65 | // we SIGKILL the whole group (negative PID) to take them all out |
| 66 | // together — otherwise children get reparented to launchd and keep |
| 67 | // holding MPS-wired memory until the user kills them manually. |
| 68 | detached: process.platform !== 'win32', |
| 69 | }) |
| 70 | |
| 71 | this.process.stdout?.on('data', (data) => { |
| 72 | const msg = data.toString().trim() |
| 73 | console.log('[FastAPI]', msg) |
| 74 | logger.python(msg) |
| 75 | this.emitTqdmLog(msg) |
| 76 | }) |
| 77 | |
| 78 | this.process.stderr?.on('data', (data) => { |
| 79 | const msg = data.toString().trim() |
| 80 | console.error('[FastAPI]', msg) |
| 81 | logger.python(`[stderr] ${msg}`) |
| 82 | this.emitTqdmLog(msg) |
| 83 | }) |
| 84 | |
| 85 | this.process.on('exit', (code) => { |
| 86 | const wasReady = this.ready |
| 87 | console.log('[PythonBridge] Process exited with code', code) |
| 88 | this.ready = false |
| 89 | this.process = null |
| 90 | if (wasReady && !this.intentionalStop) { |
| 91 | const getWindow = this.getWindow |
| 92 | if (!getWindow) return |
| 93 | const win = getWindow() |
no test coverage detected