diff --git a/core/llm/llms/Aider.ts b/core/llm/llms/Aider.ts index bb4cb89b21..df78785f42 100644 --- a/core/llm/llms/Aider.ts +++ b/core/llm/llms/Aider.ts @@ -9,11 +9,14 @@ import { BaseLLM } from "../index.js"; import { streamSse, streamJSON } from "../stream.js"; import { checkTokens } from "../../db/token.js"; import { stripImages } from "../images.js"; -import { - countTokens, -} from "../countTokens.js"; +import { countTokens } from "../countTokens.js"; import * as cp from 'child_process'; import * as process from 'process'; +import { PearAICredentials } from "../../pearaiServer/PearAICredentials.js"; +import { getHeaders } from "../../pearaiServer/stubs/headers.js"; +import { execSync } from 'child_process'; +import * as os from 'os'; + export const AIDER_QUESTION_MARKER = "[Yes]\\:" export const AIDER_END_MARKER = '─────────────────────────────────────' @@ -32,22 +35,71 @@ class Aider extends BaseLLM { private aiderProcess: cp.ChildProcess | null = null; private aiderOutput: string = ''; + private credentials: PearAICredentials; constructor(options: LLMOptions) { super(options); if (options.getCurrentDirectory) { this.getCurrentDirectory = options.getCurrentDirectory; } + this.credentials = new PearAICredentials( + options.getCredentials, + options.setCredentials || (async () => {}) + ); console.log("Aider constructor called"); this.startAiderChat(this.model, this.apiKey); } + public setPearAIAccessToken(value: string | undefined): void { + this.credentials.setAccessToken(value); + } + + public setPearAIRefreshToken(value: string | undefined): void { + this.credentials.setRefreshToken(value); + } + + private getUserShell(): string { + if (os.platform() === 'win32') { + return process.env.COMSPEC || 'cmd.exe'; + } + return process.env.SHELL || '/bin/sh'; + } + + private getUserPath(): string { + try { + let command: string; + const shell = this.getUserShell(); + + if (os.platform() === 'win32') { + // For Windows, we'll use a PowerShell command + command = 'powershell -Command "[Environment]::GetEnvironmentVariable(\'Path\', \'User\') + \';\' + [Environment]::GetEnvironmentVariable(\'Path\', \'Machine\')"'; + } else { + // For Unix-like systems (macOS, Linux) + command = `${shell} -ilc 'echo $PATH'`; + } + + return execSync(command, { encoding: 'utf8' }).trim(); + } catch (error) { + console.error('Error getting user PATH:', error); + return process.env.PATH || ''; + } + } + + + + private async _getHeaders() { + await this.credentials.checkAndUpdateCredentials(); + return { + "Content-Type": "application/json", + ...(await getHeaders()), + }; + } + private captureAiderOutput(data: Buffer): void { const lines = data.toString().replace(/\r\n|\r/g, '').split('\n'); console.log("Before lines:", lines) let startIndex = 0; for (let i = 0; i < lines.length; i++) { - // Look for [Yes] with any spaces in between due to terminal buffer edge bases if (lines[i].match(/\[\s*Y\s*e\s*s\s*\]\s*:\s*/i) && !lines[i].trim().endsWith(':')) { startIndex = i + 1; break; @@ -63,19 +115,14 @@ class Aider extends BaseLLM { this.aiderOutput += filteredOutput; } - startAiderChat(model: string, apiKey: string | undefined): Promise { + async startAiderChat(model: string, apiKey: string | undefined): Promise { return new Promise(async (resolve, reject) => { let currentDir: string; if (this.getCurrentDirectory) { - console.log("Current directory function isNOT null"); currentDir = await this.getCurrentDirectory(); - console.log("Current directory:", currentDir); } else { - console.log("Current directory function is null"); - currentDir = "/Users/nang/Documents/ebook-generator-site/"; // Todo: make this the dir that is currently open + currentDir = ""; } - console.log("Current directory:", currentDir); - let command: string; switch (model) { @@ -86,18 +133,64 @@ class Aider extends BaseLLM { command = `export OPENAI_API_KEY=${apiKey}; /opt/homebrew/bin/aider`; break; case "pearai_model": - default: - command = '/opt/homebrew/bin/aider --openai-api-key 8888 --openai-api-base http://localhost:8000/integrations/aider'; - break; + default: + await this.credentials.checkAndUpdateCredentials(); + const accessToken = this.credentials.getAccessToken(); + command = `aider --openai-api-key ${accessToken} --openai-api-base http://localhost:8000/integrations/aider`; + break; + } + + const userPath = this.getUserPath(); + const userShell = this.getUserShell(); + console.log('User PATH:', userPath); + console.log('User shell:', userShell); + + let spawnArgs: string[]; + if (os.platform() === 'win32') { + spawnArgs = ['/c', command]; + } else { + spawnArgs = ['-c', command]; } - this.aiderProcess = cp.spawn('bash', ['-c', command], { + this.aiderProcess = cp.spawn(userShell, spawnArgs, { stdio: ['pipe', 'pipe', 'pipe'], cwd: currentDir, env: { ...process.env, + PATH: userPath, // Use the user's PATH here } }); + const spawnAiderProcess = () => { + return cp.spawn(userShell, spawnArgs, { + stdio: ['pipe', 'pipe', 'pipe'], + cwd: currentDir, + env: { + ...process.env, + PATH: userPath, + } + }); + }; + + try { + this.aiderProcess = spawnAiderProcess(); + + if (!this.aiderProcess.pid) { + console.log('Aider not found. Attempting to install...'); + execSync('python -m pip install -U --upgrade-strategy only-if-needed aider-chat', { stdio: 'inherit' }); + console.log('Aider installed successfully. Retrying...'); + this.aiderProcess = spawnAiderProcess(); + } + + if (!this.aiderProcess.pid) { + throw new Error('Failed to start Aider after installation'); + } + } catch (error) { + console.error('Failed to start or install Aider:', error); + reject(error); + return; + } + + console.log('Spawned Aider process with PATH:', userPath); if (this.aiderProcess.stdout && this.aiderProcess.stderr) { this.aiderProcess.stdout.on('data', (data: Buffer) => {