mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 09:09:46 +01:00
Stop copying node-pty into Copilot CLI SDK (#310925)
* Stop copying node-pty into Copilot CLI SDK * reanem Co-authored-by: Copilot <copilot@github.com> * resovle copilot comment * recognize other sdk prebuilds NOT node-pty Co-authored-by: Copilot <copilot@github.com> --------- Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -34,7 +34,7 @@ import * as cp from 'child_process';
|
||||
import log from 'fancy-log';
|
||||
import buildfile from './buildfile.ts';
|
||||
import { fetchUrls, fetchGithub } from './lib/fetch.ts';
|
||||
import { getCopilotExcludeFilter, copyCopilotNativeDeps, prepareBuiltInCopilotExtensionShims } from './lib/copilot.ts';
|
||||
import { getCopilotExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
|
||||
import jsonEditor from 'gulp-json-editor';
|
||||
|
||||
|
||||
@@ -463,14 +463,13 @@ function patchWin32DependenciesTask(destinationFolderName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function copyCopilotNativeDepsTaskREH(platform: string, arch: string, destinationFolderName: string) {
|
||||
function prepareCopilotRipgrepShimTaskREH(platform: string, arch: string, destinationFolderName: string) {
|
||||
return async () => {
|
||||
const outputDir = path.join(BUILD_ROOT, destinationFolderName);
|
||||
const nodeModulesDir = path.join(outputDir, 'node_modules');
|
||||
copyCopilotNativeDeps(platform, arch, nodeModulesDir);
|
||||
|
||||
const builtInCopilotExtensionDir = path.join(outputDir, 'extensions', 'copilot');
|
||||
prepareBuiltInCopilotExtensionShims(platform, arch, builtInCopilotExtensionDir, nodeModulesDir);
|
||||
prepareBuiltInCopilotRipgrepShim(platform, arch, builtInCopilotExtensionDir, nodeModulesDir);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -523,7 +522,7 @@ function tweakProductForServerWeb(product: typeof import('../product.json')) {
|
||||
gulp.task(`node-${platform}-${arch}`) as task.Task,
|
||||
util.rimraf(path.join(BUILD_ROOT, destinationFolderName)),
|
||||
packageTask(type, platform, arch, sourceFolderName, destinationFolderName),
|
||||
copyCopilotNativeDepsTaskREH(platform, arch, destinationFolderName)
|
||||
prepareCopilotRipgrepShimTaskREH(platform, arch, destinationFolderName)
|
||||
];
|
||||
|
||||
if (platform === 'win32') {
|
||||
|
||||
@@ -31,7 +31,7 @@ import minimist from 'minimist';
|
||||
import { compileBuildWithoutManglingTask, compileBuildWithManglingTask } from './gulpfile.compile.ts';
|
||||
import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask, compileCopilotExtensionBuildTask } from './gulpfile.extensions.ts';
|
||||
import { copyCodiconsTask } from './lib/compilation.ts';
|
||||
import { getCopilotExcludeFilter, copyCopilotNativeDeps, prepareBuiltInCopilotExtensionShims } from './lib/copilot.ts';
|
||||
import { getCopilotExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
|
||||
import type { EmbeddedProductInfo } from './lib/embeddedType.ts';
|
||||
import { useEsbuildTranspile } from './buildConfig.ts';
|
||||
import { promisify } from 'util';
|
||||
@@ -700,7 +700,7 @@ function patchWin32DependenciesTask(destinationFolderName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFolderName: string) {
|
||||
function prepareCopilotRipgrepShimTask(platform: string, arch: string, destinationFolderName: string) {
|
||||
const outputDir = path.join(path.dirname(root), destinationFolderName);
|
||||
|
||||
return async () => {
|
||||
@@ -711,10 +711,9 @@ function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFo
|
||||
? path.join(outputDir, `${product.nameLong}.app`, 'Contents', 'Resources', 'app')
|
||||
: path.join(outputDir, versionedResourcesFolder, 'resources', 'app');
|
||||
const appNodeModulesDir = path.join(appBase, 'node_modules');
|
||||
copyCopilotNativeDeps(platform, arch, appNodeModulesDir);
|
||||
|
||||
const builtInCopilotExtensionDir = path.join(appBase, 'extensions', 'copilot');
|
||||
prepareBuiltInCopilotExtensionShims(platform, arch, builtInCopilotExtensionDir, appNodeModulesDir);
|
||||
prepareBuiltInCopilotRipgrepShim(platform, arch, builtInCopilotExtensionDir, appNodeModulesDir);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -743,7 +742,7 @@ BUILD_TARGETS.forEach(buildTarget => {
|
||||
compileNativeExtensionsBuildTask,
|
||||
util.rimraf(path.join(buildRoot, destinationFolderName)),
|
||||
packageTask(platform, arch, sourceFolderName, destinationFolderName, opts),
|
||||
copyCopilotNativeDepsTask(platform, arch, destinationFolderName)
|
||||
prepareCopilotRipgrepShimTask(platform, arch, destinationFolderName)
|
||||
];
|
||||
|
||||
if (platform === 'win32') {
|
||||
|
||||
+17
-69
@@ -46,8 +46,9 @@ function toNodePlatformArch(platform: string, arch: string): { nodePlatform: str
|
||||
* for architectures other than the build target.
|
||||
*
|
||||
* For platforms the copilot SDK doesn't natively support (e.g. alpine, armhf),
|
||||
* ALL platform packages are stripped - that's fine because the SDK doesn't ship
|
||||
* binaries for those platforms anyway, and we replace them with VS Code's own.
|
||||
* ALL platform packages are stripped - that's fine because the copilot CLI SDK
|
||||
* resolves `node-pty` from the embedder (VS Code) first via `hostRequire`,
|
||||
* falling back to its bundled copy only if the embedder can't provide it.
|
||||
*/
|
||||
export function getCopilotExcludeFilter(platform: string, arch: string): string[] {
|
||||
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
|
||||
@@ -55,74 +56,30 @@ export function getCopilotExcludeFilter(platform: string, arch: string): string[
|
||||
const nonTargetPlatforms = copilotPlatforms.filter(p => p !== targetPlatformArch);
|
||||
|
||||
// Strip wrong-architecture @github/copilot-{platform} packages.
|
||||
// All copilot prebuilds are stripped by .moduleignore; VS Code's own
|
||||
// node-pty is copied into the prebuilds location by a post-packaging task.
|
||||
// All copilot prebuilds are stripped by .moduleignore; the copilot CLI SDK
|
||||
// resolves `node-pty` from VS Code's own node_modules via `hostRequire`.
|
||||
const excludes = nonTargetPlatforms.map(p => `!**/node_modules/@github/copilot-${p}/**`);
|
||||
|
||||
return ['**', ...excludes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies VS Code's own node-pty binaries into the copilot SDK's
|
||||
* expected locations so the copilot CLI subprocess can find them at runtime.
|
||||
* The copilot-bundled prebuilds are stripped by .moduleignore;
|
||||
* this replaces them with the same binaries VS Code already ships, avoiding
|
||||
* new system dependency requirements.
|
||||
*
|
||||
* This works even for platforms the copilot SDK doesn't natively support
|
||||
* (e.g. alpine, armhf) because the SDK's native module loader simply
|
||||
* looks for `prebuilds/{process.platform}-{process.arch}/pty.node` - it
|
||||
* doesn't validate the platform against a supported list.
|
||||
*
|
||||
* Failures are logged but do not throw, to avoid breaking the build on
|
||||
* platforms where something unexpected happens.
|
||||
*
|
||||
* @param nodeModulesDir Absolute path to the node_modules directory that
|
||||
* contains both the source binaries (node-pty) and the copilot SDK
|
||||
* target directories.
|
||||
*/
|
||||
export function copyCopilotNativeDeps(platform: string, arch: string, nodeModulesDir: string): void {
|
||||
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
|
||||
const platformArch = `${nodePlatform}-${nodeArch}`;
|
||||
|
||||
const copilotBase = path.join(nodeModulesDir, '@github', 'copilot');
|
||||
if (!fs.existsSync(copilotBase)) {
|
||||
console.warn(`[copyCopilotNativeDeps] @github/copilot not found at ${copilotBase}, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
const nodePtySource = path.join(nodeModulesDir, 'node-pty', 'build', 'Release');
|
||||
if (!fs.existsSync(nodePtySource)) {
|
||||
console.warn(`[copyCopilotNativeDeps] node-pty source not found at ${nodePtySource}, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Copy node-pty (pty.node + spawn-helper on Unix, conpty.node + conpty/ on Windows)
|
||||
// into copilot prebuilds so the SDK finds them via loadNativeModule.
|
||||
const copilotPrebuildsDir = path.join(copilotBase, 'prebuilds', platformArch);
|
||||
fs.mkdirSync(copilotPrebuildsDir, { recursive: true });
|
||||
fs.cpSync(nodePtySource, copilotPrebuildsDir, { recursive: true });
|
||||
console.log(`[copyCopilotNativeDeps] Copied node-pty from ${nodePtySource} to ${copilotPrebuildsDir}`);
|
||||
} catch (err) {
|
||||
console.warn(`[copyCopilotNativeDeps] Failed to copy node-pty for ${platformArch}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Materializes copilot CLI shims directly inside the built-in copilot extension.
|
||||
* Materializes the copilot CLI ripgrep shim directly inside the built-in copilot extension.
|
||||
*
|
||||
* This is used when copilot is shipped as a built-in extension so startup does
|
||||
* not need to create shims at runtime. The destination layout matches the
|
||||
* not need to create the shim at runtime. The destination layout matches the
|
||||
* runtime shim logic in the copilot extension:
|
||||
* - node-pty: node_modules/@github/copilot/sdk/prebuilds/{platform-arch}
|
||||
* - ripgrep: node_modules/@github/copilot/sdk/ripgrep/bin/{platform-arch}
|
||||
* - marker: node_modules/@github/copilot/shims.txt
|
||||
*
|
||||
* Note: `node-pty` is no longer shimmed. The copilot CLI SDK resolves
|
||||
* `node-pty` from the embedder (VS Code) via `hostRequire` and falls back to
|
||||
* its bundled copy only if that fails.
|
||||
*
|
||||
* Failures throw to fail the build because built-in packaging must guarantee
|
||||
* these artifacts are present.
|
||||
* this artifact is present.
|
||||
*/
|
||||
export function prepareBuiltInCopilotExtensionShims(platform: string, arch: string, builtInCopilotExtensionDir: string, appNodeModulesDir: string): void {
|
||||
export function prepareBuiltInCopilotRipgrepShim(platform: string, arch: string, builtInCopilotExtensionDir: string, appNodeModulesDir: string): void {
|
||||
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
|
||||
const platformArch = `${nodePlatform}-${nodeArch}`;
|
||||
|
||||
@@ -130,33 +87,24 @@ export function prepareBuiltInCopilotExtensionShims(platform: string, arch: stri
|
||||
const copilotBase = path.join(extensionNodeModules, '@github', 'copilot');
|
||||
const copilotSdkBase = path.join(copilotBase, 'sdk');
|
||||
if (!fs.existsSync(copilotSdkBase)) {
|
||||
throw new Error(`[prepareBuiltInCopilotExtensionShims] Copilot SDK directory not found at ${copilotSdkBase}`);
|
||||
}
|
||||
|
||||
const nodePtySource = path.join(appNodeModulesDir, 'node-pty', 'build', 'Release');
|
||||
if (!fs.existsSync(nodePtySource)) {
|
||||
throw new Error(`[prepareBuiltInCopilotExtensionShims] node-pty source not found at ${nodePtySource}`);
|
||||
throw new Error(`[prepareBuiltInCopilotRipgrepShim] Copilot SDK directory not found at ${copilotSdkBase}`);
|
||||
}
|
||||
|
||||
const ripgrepSource = path.join(appNodeModulesDir, '@vscode', 'ripgrep', 'bin');
|
||||
if (!fs.existsSync(ripgrepSource)) {
|
||||
throw new Error(`[prepareBuiltInCopilotExtensionShims] ripgrep source not found at ${ripgrepSource}`);
|
||||
throw new Error(`[prepareBuiltInCopilotRipgrepShim] ripgrep source not found at ${ripgrepSource}`);
|
||||
}
|
||||
|
||||
const nodePtyDest = path.join(copilotSdkBase, 'prebuilds', platformArch);
|
||||
const ripgrepDest = path.join(copilotSdkBase, 'ripgrep', 'bin', platformArch);
|
||||
const shimMarkerPath = path.join(copilotBase, 'shims.txt');
|
||||
|
||||
try {
|
||||
fs.mkdirSync(nodePtyDest, { recursive: true });
|
||||
fs.cpSync(nodePtySource, nodePtyDest, { recursive: true });
|
||||
|
||||
fs.mkdirSync(ripgrepDest, { recursive: true });
|
||||
fs.cpSync(ripgrepSource, ripgrepDest, { recursive: true });
|
||||
|
||||
fs.writeFileSync(shimMarkerPath, 'Shims created successfully');
|
||||
console.log(`[prepareBuiltInCopilotExtensionShims] Materialized shims for ${platformArch} in ${builtInCopilotExtensionDir}`);
|
||||
console.log(`[prepareBuiltInCopilotRipgrepShim] Materialized ripgrep shim for ${platformArch} in ${builtInCopilotExtensionDir}`);
|
||||
} catch (err) {
|
||||
throw new Error(`[prepareBuiltInCopilotExtensionShims] Failed to materialize shims for ${platformArch}: ${err}`);
|
||||
throw new Error(`[prepareBuiltInCopilotRipgrepShim] Failed to materialize ripgrep shim for ${platformArch}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +338,6 @@ export default tseslint.config(
|
||||
ignores: [
|
||||
'src/util/vs/**/*.ts', // vendored code
|
||||
'src/**/*.spec.ts', // allow in tests
|
||||
'./src/extension/agents/copilotcli/node/nodePtyShim.ts',
|
||||
'./src/extension/byok/common/anthropicMessageConverter.ts',
|
||||
'./src/extension/byok/common/geminiFunctionDeclarationConverter.ts',
|
||||
'./src/extension/byok/common/geminiMessageConverter.ts',
|
||||
|
||||
@@ -78,7 +78,6 @@ copilotcli/
|
||||
│ ├── copilotCLISkills.ts # Skills location resolution
|
||||
│ ├── copilotCLIImageSupport.ts # Image attachment handling
|
||||
│ ├── mcpHandler.ts # MCP server configuration for SDK sessions
|
||||
│ ├── nodePtyShim.ts # Copies VS Code's node-pty for SDK use
|
||||
│ ├── userInputHelpers.ts # User question/input handling interface
|
||||
│ ├── exitPlanModeHandler.ts # Plan mode exit flow with user choice
|
||||
│ ├── ripgrepShim.ts # Copies VS Code's ripgrep for SDK use
|
||||
@@ -313,7 +312,7 @@ Orchestrates the start and end of each chat request turn, coordinating worktree
|
||||
|
||||
## Critical Pitfalls
|
||||
|
||||
- **Shims before SDK import**: `ensureNodePtyShim()` and `ensureRipgrepShim()` in `node/nodePtyShim.ts` / `node/ripgrepShim.ts` MUST be called before any `import('@github/copilot/sdk')`. They copy VS Code's bundled native binaries to the SDK's expected locations. See `node/copilotCli.ts` for the initialization order.
|
||||
- **Shims before SDK import**: `ensureRipgrepShim()` in `node/ripgrepShim.ts` MUST be called before any `import('@github/copilot/sdk')`. It copies VS Code's bundled ripgrep binary to the SDK's expected location. `node-pty` is no longer shimmed: the copilot CLI SDK resolves it from VS Code's own `node_modules` via `hostRequire`, falling back to its bundled copy only if that fails. See `node/copilotCli.ts` for the initialization order.
|
||||
|
||||
- **Delayed permission UI**: Tool invocation messages are held in `toolCallWaitingForPermissions` until permission resolves. `flushPendingInvocationMessageForToolCallId()` flushes only the specific approved tool, not all pending tools. This is intentional — don't bypass it.
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { basename } from '../../../../util/vs/base/common/resources';
|
||||
import { URI } from '../../../../util/vs/base/common/uri';
|
||||
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
|
||||
import { getCopilotLogger } from './logger';
|
||||
import { ensureNodePtyShim } from './nodePtyShim';
|
||||
import { ensureRipgrepShim } from './ripgrepShim';
|
||||
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
|
||||
|
||||
@@ -460,7 +459,7 @@ export class CopilotCLISDK implements ICopilotCLISDK {
|
||||
|
||||
public async getPackage(): Promise<typeof import('@github/copilot/sdk')> {
|
||||
try {
|
||||
// Ensure the node-pty shim exists before importing the SDK (required for CLI sessions)
|
||||
// Ensure the ripgrep shim exists before importing the SDK (required for CLI sessions)
|
||||
await this._ensureShimsPromise;
|
||||
return await import('@github/copilot/sdk');
|
||||
} catch (error) {
|
||||
@@ -497,10 +496,7 @@ export class CopilotCLISDK implements ICopilotCLISDK {
|
||||
if (await checkFileExists(successfulPlaceholder)) {
|
||||
return;
|
||||
}
|
||||
await Promise.all([
|
||||
ensureNodePtyShim(this.extensionContext.extensionPath, this.envService.appRoot, this.logService),
|
||||
ensureRipgrepShim(this.extensionContext.extensionPath, this.envService.appRoot, this.logService)
|
||||
]);
|
||||
await ensureRipgrepShim(this.extensionContext.extensionPath, this.envService.appRoot, this.logService);
|
||||
await fs.writeFile(successfulPlaceholder, 'Shims created successfully');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ILogService } from '../../../../platform/log/common/logService';
|
||||
|
||||
let shimCreated: Promise<void> | undefined = undefined;
|
||||
|
||||
const RETRIABLE_COPY_ERROR_CODES = new Set(['EPERM', 'EBUSY']);
|
||||
const MAX_COPY_ATTEMPTS = 6;
|
||||
const RETRY_DELAY_BASE_MS = 50;
|
||||
const RETRY_DELAY_CAP_MS = 500;
|
||||
const MATERIALIZATION_TIMEOUT_MS = 4000;
|
||||
const MATERIALIZATION_POLL_INTERVAL_MS = 100;
|
||||
|
||||
/**
|
||||
* Copies the node-pty files from VS Code's installation into a @github/copilot location
|
||||
*
|
||||
* MUST be called before any `import('@github/copilot/sdk')` or `import('@github/copilot')`.
|
||||
*
|
||||
* @github/copilot bundles the node-pty code and its no longer possible to shim the package.
|
||||
*
|
||||
* @param extensionPath The extension's path (where to create the shim)
|
||||
* @param vscodeAppRoot VS Code's installation path (where node-pty is located)
|
||||
*/
|
||||
export async function ensureNodePtyShim(extensionPath: string, vscodeAppRoot: string, logService: ILogService): Promise<void> {
|
||||
if (shimCreated) {
|
||||
return shimCreated;
|
||||
}
|
||||
|
||||
const creation = _ensureNodePtyShim(extensionPath, vscodeAppRoot, logService);
|
||||
shimCreated = creation.catch(error => {
|
||||
shimCreated = undefined;
|
||||
throw error;
|
||||
});
|
||||
return shimCreated;
|
||||
}
|
||||
|
||||
async function _ensureNodePtyShim(extensionPath: string, vscodeAppRoot: string, logService: ILogService): Promise<void> {
|
||||
const vscodeNodePtyPath = path.join(vscodeAppRoot, 'node_modules', 'node-pty', 'build', 'Release');
|
||||
|
||||
await copyNodePtyFiles(extensionPath, vscodeNodePtyPath, logService);
|
||||
}
|
||||
|
||||
export async function copyNodePtyFiles(extensionPath: string, sourceNodePtyPath: string, logService: ILogService): Promise<void> {
|
||||
const nodePtyDir = path.join(extensionPath, 'node_modules', '@github', 'copilot', 'sdk', 'prebuilds', process.platform + '-' + process.arch);
|
||||
logService.info(`Creating node-pty shim: source=${sourceNodePtyPath}, dest=${nodePtyDir}`);
|
||||
|
||||
try {
|
||||
await fs.mkdir(nodePtyDir, { recursive: true });
|
||||
const entries = await fs.readdir(sourceNodePtyPath);
|
||||
const uniqueEntries = [...new Set(entries)];
|
||||
logService.info(`Found ${uniqueEntries.length} entries to copy${uniqueEntries.length !== entries.length ? ` (${entries.length - uniqueEntries.length} duplicates ignored)` : ''}: ${uniqueEntries.join(', ')}`);
|
||||
|
||||
await copyNodePtyWithRetries(sourceNodePtyPath, nodePtyDir, uniqueEntries, logService);
|
||||
} catch (error) {
|
||||
logService.error(`Failed to create node-pty shim (source dir: ${sourceNodePtyPath}, extension dir: ${nodePtyDir})`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyNodePtyWithRetries(sourceDir: string, destDir: string, entries: string[], logService: ILogService): Promise<void> {
|
||||
const primaryBinary = entries.find(entry => entry.endsWith('.node'));
|
||||
for (let attempt = 1; attempt <= MAX_COPY_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
await fs.cp(sourceDir, destDir, {
|
||||
recursive: true,
|
||||
dereference: true,
|
||||
force: true,
|
||||
filter: async (srcPath) => shouldCopyEntry(srcPath, logService)
|
||||
});
|
||||
logService.trace(`Copied node-pty prebuilds to ${destDir} (attempt ${attempt})`);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (await waitForMaterializedShim(destDir, primaryBinary, logService)) {
|
||||
logService.trace(`Detected node-pty shim materialized at ${destDir} by another extension host`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RETRIABLE_COPY_ERROR_CODES.has(error?.code) || attempt === MAX_COPY_ATTEMPTS) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const delayMs = Math.min(RETRY_DELAY_BASE_MS * Math.pow(2, attempt - 1), RETRY_DELAY_CAP_MS);
|
||||
logService.warn(`Retryable error (${error.code}) copying node-pty shim. Retrying in ${delayMs}ms (attempt ${attempt + 1}/${MAX_COPY_ATTEMPTS})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function shouldCopyEntry(srcPath: string, logService: ILogService): Promise<boolean> {
|
||||
try {
|
||||
const stat = await fs.stat(srcPath);
|
||||
if (stat.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stat.size === 0) {
|
||||
logService.trace(`Skipping ${path.basename(srcPath)}: zero-byte file (likely symlink or special file)`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logService.warn(`Failed to stat ${srcPath}: ${error?.message ?? error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForMaterializedShim(destDir: string, primaryBinary: string | undefined, logService: ILogService): Promise<boolean> {
|
||||
const deadline = Date.now() + MATERIALIZATION_TIMEOUT_MS;
|
||||
while (Date.now() <= deadline) {
|
||||
if (await isShimMaterialized(destDir, primaryBinary)) {
|
||||
logService.trace(`Reusing node-pty shim that materialized at ${destDir}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, MATERIALIZATION_POLL_INTERVAL_MS));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function isShimMaterialized(destDir: string, primaryBinary: string | undefined): Promise<boolean> {
|
||||
if (primaryBinary) {
|
||||
const binaryStat = await fs.stat(path.join(destDir, primaryBinary)).catch(() => undefined);
|
||||
if (binaryStat && binaryStat.isFile() && binaryStat.size > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(destDir).catch(() => []);
|
||||
for (const entry of entries) {
|
||||
const stat = await fs.stat(path.join(destDir, entry)).catch(() => undefined);
|
||||
if (stat && stat.isFile() && stat.size > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
+15
-14
@@ -8,7 +8,6 @@ import { isBinaryFile } from 'isbinaryfile';
|
||||
import * as path from 'path';
|
||||
import { beforeAll, describe, it } from 'vitest';
|
||||
import { TestLogService } from '../../../../platform/testing/common/testLogService';
|
||||
import { copyNodePtyFiles } from '../../copilotcli/node/nodePtyShim';
|
||||
import { copyRipgrepShim } from '../../copilotcli/node/ripgrepShim';
|
||||
|
||||
describe('CopilotCLI SDK Upgrade', function () {
|
||||
@@ -59,6 +58,16 @@ describe('CopilotCLI SDK Upgrade', function () {
|
||||
// win32 native module (formerly win_error_mode)
|
||||
path.join('prebuilds', 'win32-arm64', 'win32.node'),
|
||||
path.join('prebuilds', 'win32-x64', 'win32.node'),
|
||||
// Second copy of computer.node / win32.node re-shipped by the @github/copilot/sdk subpackage
|
||||
// (previously hidden by a broad sdk/prebuilds/** exclusion that masked the node-pty files we used to shim in at test setup).
|
||||
path.join('sdk', 'prebuilds', 'darwin-arm64', 'computer.node'),
|
||||
path.join('sdk', 'prebuilds', 'darwin-x64', 'computer.node'),
|
||||
path.join('sdk', 'prebuilds', 'linux-arm64', 'computer.node'),
|
||||
path.join('sdk', 'prebuilds', 'linux-x64', 'computer.node'),
|
||||
path.join('sdk', 'prebuilds', 'win32-arm64', 'computer.node'),
|
||||
path.join('sdk', 'prebuilds', 'win32-x64', 'computer.node'),
|
||||
path.join('sdk', 'prebuilds', 'win32-arm64', 'win32.node'),
|
||||
path.join('sdk', 'prebuilds', 'win32-x64', 'win32.node'),
|
||||
path.join('ripgrep', 'bin', 'darwin-arm64', 'rg'),
|
||||
path.join('ripgrep', 'bin', 'darwin-x64', 'rg'),
|
||||
path.join('ripgrep', 'bin', 'linux-x64', 'rg'),
|
||||
@@ -89,15 +98,13 @@ describe('CopilotCLI SDK Upgrade', function () {
|
||||
'tree-sitter-scala.wasm',
|
||||
].map(p => path.join(copilotSDKPath, p)));
|
||||
|
||||
// Exclude ripgrep files that we copy over in src/extension/agents/copilotcli/node/ripgrepShim.ts (until we get better API/solution from SDK)
|
||||
// Exclude ripgrep files that we copy over in src/extension/chatSessions/copilotcli/node/ripgrepShim.ts (until we get better API/solution from SDK)
|
||||
const ripgrepFilesWeCopy = path.join(copilotSDKPath, 'sdk', 'ripgrep', 'bin');
|
||||
// Exclude nodepty files that we copy over in src/extension/agents/copilotcli/node/nodePtyShim.ts (until we get better API/solution from SDK)
|
||||
const nodeptyFilesWeCopy = path.join(copilotSDKPath, 'sdk', 'prebuilds');
|
||||
|
||||
const errors: string[] = [];
|
||||
// Look for new binaries
|
||||
for (const binary of existingBinaries) {
|
||||
if (binary.startsWith(ripgrepFilesWeCopy) || binary.startsWith(nodeptyFilesWeCopy)) {
|
||||
if (binary.startsWith(ripgrepFilesWeCopy)) {
|
||||
continue;
|
||||
}
|
||||
const binaryName = path.basename(binary);
|
||||
@@ -110,7 +117,7 @@ describe('CopilotCLI SDK Upgrade', function () {
|
||||
}
|
||||
// Look for removed binaries.
|
||||
for (const binary of knownBinaries) {
|
||||
if (binary.startsWith(ripgrepFilesWeCopy) || binary.startsWith(nodeptyFilesWeCopy)) {
|
||||
if (binary.startsWith(ripgrepFilesWeCopy)) {
|
||||
continue;
|
||||
}
|
||||
if (!existingBinaries.has(binary)) {
|
||||
@@ -124,19 +131,13 @@ describe('CopilotCLI SDK Upgrade', function () {
|
||||
});
|
||||
|
||||
it('should be able to load the @github/copilot module without errors', async function () {
|
||||
await copyNodePtyFiles(
|
||||
extensionPath,
|
||||
path.join(copilotSDKPath, 'prebuilds', process.platform + '-' + process.arch),
|
||||
new TestLogService()
|
||||
);
|
||||
await import('@github/copilot/sdk');
|
||||
});
|
||||
});
|
||||
|
||||
async function copyBinaries(extensionPath: string) {
|
||||
const nodePtyPrebuilds = path.join(extensionPath, 'node_modules', '@github', 'copilot', 'prebuilds', process.platform + '-' + process.arch);
|
||||
const vscodeRipgrepPath = path.join(extensionPath, 'node_modules', '@github', 'copilot', 'ripgrep', 'bin', process.platform + '-' + process.arch);
|
||||
await copyNodePtyFiles(extensionPath, nodePtyPrebuilds, new TestLogService());
|
||||
const copilotSDKPath = path.join(extensionPath, 'node_modules', '@github', 'copilot');
|
||||
const vscodeRipgrepPath = path.join(copilotSDKPath, 'ripgrep', 'bin', process.platform + '-' + process.arch);
|
||||
await copyRipgrepShim(extensionPath, vscodeRipgrepPath, new TestLogService());
|
||||
}
|
||||
async function findAllBinaries(dir: string): Promise<string[]> {
|
||||
|
||||
Reference in New Issue
Block a user