Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 57 additions & 22 deletions extensions/codestory/src/utilities/gcpBucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,66 @@ function ensureDirectoryExists(filePath: string): void {
}

export const downloadSidecarZip = async (
destination: string,
version: string = 'latest'
destination: string,
version: string = 'latest'
) => {
ensureDirectoryExists(destination);
try {
ensureDirectoryExists(destination);

const platform = process.platform;
const architecture = process.arch;
const source = `${version}/${platform}/${architecture}/sidecar.zip`;
try {
await downloadUsingURL(source, destination);
} catch (err) {
console.error(err);
throw new Error(`Failed to download sidecar`);
}
const platform = process.platform;
const architecture = process.arch;
const source = `${version}/${platform}/${architecture}/sidecar.zip`;
console.log(`Downloading sidecar for ${platform}-${architecture} from version ${version}`);

await downloadUsingURL(source, destination);
console.log('Successfully downloaded sidecar binary');
} catch (err) {
console.error('Failed to download sidecar:', err);
if (err.response) {
console.error('Response status:', err.response.status);
console.error('Response data:', err.response.data);
}
throw new Error(`Failed to download sidecar: ${err.message}`);
}
};

const downloadUsingURL = async (source: string, destination: string) => {
const url = `https://storage.googleapis.com/${BUCKET_NAME}/${source}`;
const response = await axios.get(url, { responseType: 'stream' });
const writer = fs.createWriteStream(destination);
const url = `https://storage.googleapis.com/${BUCKET_NAME}/${source}`;
console.log('Downloading from URL:', url);

try {
const response = await axios.get(url, {
responseType: 'stream',
timeout: 30000 // 30 second timeout
});

const writer = fs.createWriteStream(destination);

response.data.pipe(writer);

return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
};
return new Promise((resolve, reject) => {
response.data.pipe(writer);

let error: Error | null = null;
writer.on('error', err => {
error = err;
writer.close();
reject(err);
});

writer.on('close', () => {
if (!error) {
resolve(true);
}
// No need to reject here as it would have been handled in the error handler
});
});
} catch (err) {
if (err.code === 'ECONNREFUSED') {
throw new Error('Connection refused. Please check your internet connection.');
} else if (err.code === 'ETIMEDOUT') {
throw new Error('Connection timed out. Please try again.');
} else if (err.response && err.response.status === 404) {
throw new Error(`Sidecar binary not found for your platform (${process.platform}-${process.arch}). Please check https://aide-updates.codestory.ai for supported platforms.`);
}
throw err;
}
};
250 changes: 135 additions & 115 deletions extensions/codestory/src/utilities/setupSidecarBinary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,22 @@ async function getHealthCheckURL(): Promise<string> {
}

async function healthCheck(): Promise<boolean> {
try {
const healthCheckURL = await getHealthCheckURL();
const response = await fetch(healthCheckURL);
const isHealthy = response.status === 200;
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Connected);
return isHealthy;
} catch (e) {
console.error('Health check failed with error:', e);
return false;
}
try {
const healthCheckURL = await getHealthCheckURL();
console.log('Performing health check at:', healthCheckURL);
const response = await fetch(healthCheckURL);
const isHealthy = response.status === 200;
if (!isHealthy) {
console.error('Health check failed with status:', response.status);
console.error('Health check response:', await response.text());
}
vscode.sidecar.setRunningStatus(isHealthy ? vscode.SidecarRunningStatus.Connected : vscode.SidecarRunningStatus.Unavailable);
return isHealthy;
} catch (e) {
console.error('Health check failed with error:', e);
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
return false;
}
}

type VersionAPIResponse = {
Expand Down Expand Up @@ -338,108 +344,122 @@ export async function restartSidecarBinary(extensionBasePath: string) {
}

export async function setupSidecar(extensionBasePath: string): Promise<vscode.Disposable> {
const { zipDestination, extractedDestination, webserverPath } = getPaths(extensionBasePath);

// If user is self-managing sidecar, only do health checks
if (sidecarUseSelfRun()) {
console.log('User is self-managing sidecar binary, skipping automated setup');
const hc = await healthCheck();
if (!hc) {
console.log('Sidecar health check failed');
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
vscode.window.showWarningMessage('Sidecar is not running. Please start the sidecar binary manually as configured.');
}

// Set up recurring health check every 5 seconds
const healthCheckInterval = setInterval(async () => {
const isHealthy = await healthCheck();
if (!isHealthy) {
console.log('Sidecar health check failed');
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
} else {
versionCheck();
}
}, 5000);

return vscode.Disposable.from({ dispose: () => clearInterval(healthCheckInterval) });
}

// Regular automated setup flow
if (!fs.existsSync(webserverPath)) {
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Starting);
try {
await fetchSidecarWithProgress(zipDestination);
await unzipSidecarArchive(zipDestination, extractedDestination, webserverPath);
} catch (error) {
console.error('Failed to set up sidecar binary:', error);
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
throw error;
}
}

const hc = await healthCheck();
if (!hc) {
await startSidecarBinary(webserverPath);
}

// Asynchronously check for updates
checkForUpdates(zipDestination);

// Set up recurring health check every 5 seconds to recover sidecar
const healthCheckInterval = setInterval(async () => {
// Skip health check if we're in the middle of a restart
if (isRestarting) {
console.log('Skipping health check during restart...');
return;
}

const isHealthy = await healthCheck();
if (isHealthy) {
versionCheck();
} else {
console.log('Health check failed, attempting recovery...');
// Set to Connecting first to indicate we're trying to reconnect
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Connecting);

// First try: Attempt to restart using existing binary
try {
console.log('Attempting to restart sidecar with existing binary...');
await restartSidecarBinary(extensionBasePath);
const recoveryCheck = await retryHealthCheck(3, 1000);
if (recoveryCheck) {
console.log('Successfully recovered sidecar using existing binary');
return;
}
} catch (error) {
console.log('Failed to restart with existing binary:', error);
}

// Second try: Binary might be missing, try fresh download and start
try {
console.log('Attempting fresh download and start...');
// Kill any existing process first
await killSidecar();

// Fresh download and start
await fetchSidecarWithProgress(zipDestination);
await startSidecarBinary(webserverPath);

const freshStartCheck = await retryHealthCheck(3, 1000);
if (freshStartCheck) {
console.log('Successfully recovered sidecar with fresh download');
return;
}
} catch (error) {
console.error('Failed to recover sidecar after fresh download:', error);
}

// If we get here, all recovery attempts failed
console.error('All recovery attempts failed');
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
vscode.window.showErrorMessage('Failed to recover sidecar after multiple attempts. Please try restarting Aide.');
}
}, 5000);

// Clean up interval when extension is deactivated
return vscode.Disposable.from({ dispose: () => clearInterval(healthCheckInterval) });
}
const { zipDestination, extractedDestination, webserverPath } = getPaths(extensionBasePath);

// If user is self-managing sidecar, only do health checks
if (sidecarUseSelfRun()) {
console.log('User is self-managing sidecar binary, skipping automated setup');
const hc = await healthCheck();
if (!hc) {
console.log('Sidecar health check failed');
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
vscode.window.showErrorMessage('Sidecar is not running. Please check if the sidecar binary is running at the configured URL: ' + sidecarURL());
}

// Set up recurring health check every 5 seconds
const healthCheckInterval = setInterval(async () => {
const isHealthy = await healthCheck();
if (!isHealthy) {
console.log('Sidecar health check failed');
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
vscode.window.showErrorMessage('Lost connection to sidecar. Please check if the sidecar binary is still running.');
} else {
versionCheck();
}
}, 5000);

return vscode.Disposable.from({ dispose: () => clearInterval(healthCheckInterval) });
}

// Regular automated setup flow
if (!fs.existsSync(webserverPath)) {
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Starting);
try {
console.log('Downloading sidecar binary to:', zipDestination);
await fetchSidecarWithProgress(zipDestination);
console.log('Extracting sidecar binary to:', extractedDestination);
await unzipSidecarArchive(zipDestination, extractedDestination, webserverPath);
} catch (error) {
console.error('Failed to set up sidecar binary:', error);
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
vscode.window.showErrorMessage(`Failed to setup sidecar binary: ${error.message}. Please try restarting VS Code.`);
throw error;
}
}

const hc = await healthCheck();
if (!hc) {
try {
await startSidecarBinary(webserverPath);
} catch (error) {
console.error('Failed to start sidecar binary:', error);
vscode.window.showErrorMessage(`Failed to start sidecar binary: ${error.message}. Please try restarting VS Code.`);
throw error;
}
}

// Asynchronously check for updates
checkForUpdates(zipDestination).catch(error => {
console.error('Failed to check for updates:', error);
});

// Set up recurring health check every 5 seconds to recover sidecar
const healthCheckInterval = setInterval(async () => {
// Skip health check if we're in the middle of a restart
if (isRestarting) {
console.log('Skipping health check during restart...');
return;
}

const isHealthy = await healthCheck();
if (isHealthy) {
versionCheck();
} else {
console.log('Health check failed, attempting recovery...');
// Set to Connecting first to indicate we're trying to reconnect
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Connecting);

// First try: Attempt to restart using existing binary
try {
console.log('Attempting to restart sidecar with existing binary...');
await restartSidecarBinary(extensionBasePath);
const recoveryCheck = await retryHealthCheck(3, 1000);
if (recoveryCheck) {
console.log('Successfully recovered sidecar using existing binary');
return;
}
} catch (error) {
console.log('Failed to restart with existing binary:', error);
}

// Second try: Binary might be missing, try fresh download and start
try {
console.log('Attempting fresh download and start...');
// Kill any existing process first
await killSidecar();

// Fresh download and start
await fetchSidecarWithProgress(zipDestination);
await unzipSidecarArchive(zipDestination, extractedDestination, webserverPath);
await startSidecarBinary(webserverPath);

const freshStartCheck = await retryHealthCheck(3, 1000);
if (freshStartCheck) {
console.log('Successfully recovered sidecar with fresh download');
return;
}
} catch (error) {
console.error('Failed to recover sidecar after fresh download:', error);
vscode.window.showErrorMessage('Failed to recover sidecar. Please try manually downloading the sidecar binary from https://aide-updates.codestory.ai');
}

// If we get here, all recovery attempts failed
console.error('All recovery attempts failed');
vscode.sidecar.setRunningStatus(vscode.SidecarRunningStatus.Unavailable);
vscode.window.showErrorMessage('Failed to recover sidecar after multiple attempts. Please try restarting VS Code or manually downloading the sidecar binary.');
}
}, 5000);

// Clean up interval when extension is deactivated
return vscode.Disposable.from({ dispose: () => clearInterval(healthCheckInterval) });
}
Loading