diff --git a/build/.moduleignore b/build/.moduleignore index ed36151130c..ee3e5eb3313 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -188,3 +188,13 @@ zone.js/dist/** @xterm/xterm-addon-*/fixtures/** @xterm/xterm-addon-*/out/** @xterm/xterm-addon-*/out-test/** + +# @github/copilot - strip cross-platform binaries +@github/copilot/prebuilds/** +@github/copilot/ripgrep/** +@github/copilot/clipboard/node_modules/@teddyzhu/clipboard-darwin-x64/** +@github/copilot/clipboard/node_modules/@teddyzhu/clipboard-darwin-arm64/** +@github/copilot/clipboard/node_modules/@teddyzhu/clipboard-linux-x64-gnu/** +@github/copilot/clipboard/node_modules/@teddyzhu/clipboard-linux-arm64-gnu/** +@github/copilot/clipboard/node_modules/@teddyzhu/clipboard-win32-x64-msvc/** +@github/copilot/clipboard/node_modules/@teddyzhu/clipboard-win32-arm64-msvc/** diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 9e90e31491f..391d52bfe57 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -25,10 +25,46 @@ async function main(buildDir?: string) { const outAppPath = path.join(buildDir, `VSCode-darwin-${arch}`, appName); const productJsonPath = path.resolve(outAppPath, 'Contents', 'Resources', 'app', 'product.json'); + // Copilot SDK ships platform-specific binaries in separate npm packages with + // architecture in the package name (e.g. @github/copilot-darwin-x64, + // @github/copilot-darwin-arm64). npm only installs the one matching the host, + // so the x64 build is missing @github/copilot-darwin-arm64 and vice-versa. + // + // The universal app merger requires both builds to have identical file trees. + // To satisfy that, we copy each missing copilot platform package from the + // other build. The binaries are then excluded from comparison (filesToSkip) + // and the x64 binary is tagged as arch-specific (x64ArchFiles) so the merger + // keeps both. + // + // This workaround would go away if the SDK moved to a single @github/copilot + // package with the binary at a fixed path (like @vscode/ripgrep does). + for (const plat of ['darwin-x64', 'darwin-arm64']) { + for (const base of [ + path.join('Contents', 'Resources', 'app', 'node_modules'), + path.join('Contents', 'Resources', 'app', 'node_modules.asar.unpacked'), + ]) { + const rel = path.join(base, '@github', `copilot-${plat}`); + const inX64 = path.join(x64AppPath, rel); + const inArm64 = path.join(arm64AppPath, rel); + + if (fs.existsSync(inX64) && !fs.existsSync(inArm64)) { + fs.mkdirSync(path.dirname(inArm64), { recursive: true }); + fs.cpSync(inX64, inArm64, { recursive: true }); + } else if (fs.existsSync(inArm64) && !fs.existsSync(inX64)) { + fs.mkdirSync(path.dirname(inX64), { recursive: true }); + fs.cpSync(inArm64, inX64, { recursive: true }); + } + } + } + const filesToSkip = [ '**/CodeResources', '**/Credits.rtf', - '**/policies/{*.mobileconfig,**/*.plist}' + '**/policies/{*.mobileconfig,**/*.plist}', + '**/node_modules/@github/copilot-darwin-x64/**', + '**/node_modules/@github/copilot-darwin-arm64/**', + '**/node_modules.asar.unpacked/@github/copilot-darwin-x64/**', + '**/node_modules.asar.unpacked/@github/copilot-darwin-arm64/**', ]; await makeUniversalApp({ @@ -38,7 +74,7 @@ async function main(buildDir?: string) { outAppPath, force: true, mergeASARs: true, - x64ArchFiles: '{*/kerberos.node,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.node}', + x64ArchFiles: '{*/kerberos.node,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.node,**/node_modules/@github/copilot-darwin-*/copilot}', filesToSkipComparison: (file: string) => { for (const expected of filesToSkip) { if (minimatch(file, expected)) { diff --git a/build/darwin/verify-macho.ts b/build/darwin/verify-macho.ts index 7770b9c36cd..1884c72c227 100644 --- a/build/darwin/verify-macho.ts +++ b/build/darwin/verify-macho.ts @@ -26,6 +26,11 @@ const FILES_TO_SKIP = [ // MSAL runtime files are only present in ARM64 builds '**/extensions/microsoft-authentication/dist/libmsalruntime.dylib', '**/extensions/microsoft-authentication/dist/msal-node-runtime.node', + // Copilot SDK: universal app has both x64 and arm64 platform packages + '**/node_modules/@github/copilot-darwin-x64/**', + '**/node_modules/@github/copilot-darwin-arm64/**', + '**/node_modules.asar.unpacked/@github/copilot-darwin-x64/**', + '**/node_modules.asar.unpacked/@github/copilot-darwin-arm64/**', ]; function isFileSkipped(file: string): boolean { diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index 0dfb90f264b..4caa7b1b0fc 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -424,12 +424,26 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.{js,css}.map'])) .pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(import.meta.dirname, `.moduleignore.${process.platform}`))) + .pipe(filter((() => { + // Strip @github/copilot platform packages for wrong architectures. + const copilotPlatforms = [ + 'darwin-arm64', 'darwin-x64', + 'linux-arm64', 'linux-x64', + 'win32-arm64', 'win32-x64', + ]; + const targetPlatformArch = `${platform}-${arch}`; + const excludes = copilotPlatforms + .filter(p => p !== targetPlatformArch) + .map(p => `!**/node_modules/@github/copilot-${p}/**`); + return ['**', ...excludes]; + })())) .pipe(jsFilter) .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) .pipe(jsFilter.restore) .pipe(createAsar(path.join(process.cwd(), 'node_modules'), [ '**/*.node', '**/@vscode/ripgrep/bin/*', + '**/@github/copilot-*/**', '**/node-pty/build/Release/*', '**/node-pty/build/Release/conpty/*', '**/node-pty/lib/worker/conoutSocketWorker.js', diff --git a/build/next/index.ts b/build/next/index.ts index 77886ad43a9..c12dba55c70 100644 --- a/build/next/index.ts +++ b/build/next/index.ts @@ -101,6 +101,7 @@ const desktopEntryPoints = [ 'vs/workbench/contrib/debug/node/telemetryApp', 'vs/platform/files/node/watcher/watcherMain', 'vs/platform/terminal/node/ptyHostMain', + 'vs/platform/agent/node/agentHostMain', 'vs/workbench/api/node/extensionHostProcess', ]; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 9acc0e3fcdf..5876c247005 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -124,6 +124,7 @@ import { ElectronPtyHostStarter } from '../../platform/terminal/electron-main/el import { PtyHostService } from '../../platform/terminal/node/ptyHostService.js'; import { ElectronAgentHostStarter } from '../../platform/agent/electron-main/electronAgentHostStarter.js'; import { AgentHostProcessManager } from '../../platform/agent/node/agentHostService.js'; +import { AgentHostEnabledSettingId } from '../../platform/agent/common/agentService.js'; import { NODE_REMOTE_RESOURCE_CHANNEL_NAME, NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, NodeRemoteResourceResponse, NodeRemoteResourceRouter } from '../../platform/remote/common/electronRemoteResources.js'; import { Lazy } from '../../base/common/lazy.js'; import { IAuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/electron-main/auxiliaryWindows.js'; @@ -1110,8 +1111,10 @@ export class CodeApplication extends Disposable { services.set(ILocalPtyService, ptyHostService); // Agent Host - const agentHostStarter = new ElectronAgentHostStarter(this.environmentMainService, this.lifecycleMainService, this.logService); - this._register(new AgentHostProcessManager(agentHostStarter, this.logService, this.loggerService)); + if (this.configurationService.getValue(AgentHostEnabledSettingId)) { + const agentHostStarter = new ElectronAgentHostStarter(this.environmentMainService, this.lifecycleMainService, this.logService); + this._register(new AgentHostProcessManager(agentHostStarter, this.logService, this.loggerService)); + } // External terminal if (isWindows) { diff --git a/src/vs/platform/agent/common/agentService.ts b/src/vs/platform/agent/common/agentService.ts index 2c8afa2ec0c..7c07d8eb413 100644 --- a/src/vs/platform/agent/common/agentService.ts +++ b/src/vs/platform/agent/common/agentService.ts @@ -14,6 +14,9 @@ export const enum AgentHostIpcChannels { Logger = 'agentHostLogger', } +/** Configuration key that controls whether the agent host process is spawned. */ +export const AgentHostEnabledSettingId = 'chat.agentHost.enabled'; + // ---- IPC data types (serializable across MessagePort) ----------------------- export interface IAgentSessionMetadata { diff --git a/src/vs/platform/agent/electron-browser/agentHostService.ts b/src/vs/platform/agent/electron-browser/agentHostService.ts index f1c75ead142..0428c360ff6 100644 --- a/src/vs/platform/agent/electron-browser/agentHostService.ts +++ b/src/vs/platform/agent/electron-browser/agentHostService.ts @@ -11,8 +11,9 @@ import { getDelayedChannel, ProxyChannel } from '../../../base/parts/ipc/common/ import { Client as MessagePortClient } from '../../../base/parts/ipc/common/ipc.mp.js'; import { acquirePort } from '../../../base/parts/ipc/electron-browser/ipc.mp.js'; import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; import { ILogService } from '../../log/common/log.js'; -import { AgentHostIpcChannels, IAgentCreateSessionConfig, IAgentHostService, IAgentMessageEvent, IAgentModelInfo, IAgentProgressEvent, IAgentService, IAgentSessionMetadata, IAgentToolCompleteEvent, IAgentToolStartEvent } from '../common/agentService.js'; +import { AgentHostEnabledSettingId, AgentHostIpcChannels, IAgentCreateSessionConfig, IAgentHostService, IAgentMessageEvent, IAgentModelInfo, IAgentProgressEvent, IAgentService, IAgentSessionMetadata, IAgentToolCompleteEvent, IAgentToolStartEvent } from '../common/agentService.js'; /** * Renderer-side implementation of {@link IAgentHostService} that connects @@ -36,6 +37,7 @@ class AgentHostServiceClient extends Disposable implements IAgentHostService { constructor( @ILogService private readonly _logService: ILogService, + @IConfigurationService configurationService: IConfigurationService, ) { super(); @@ -45,7 +47,9 @@ class AgentHostServiceClient extends Disposable implements IAgentHostService { getDelayedChannel(this._clientEventually.p.then(client => client.getChannel(AgentHostIpcChannels.AgentHost))) ); - this._connect(); + if (configurationService.getValue(AgentHostEnabledSettingId)) { + this._connect(); + } } private async _connect(): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostChatContribution.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostChatContribution.ts index d302fcd2772..dc9df9b6b98 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostChatContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostChatContribution.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { IAgentHostService } from '../../../../../../platform/agent/common/agentService.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IAgentHostService, AgentHostEnabledSettingId } from '../../../../../../platform/agent/common/agentService.js'; import { IDefaultAccountService } from '../../../../../../platform/defaultAccount/common/defaultAccount.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; @@ -32,9 +33,14 @@ export class CopilotAgentHostContribution extends Disposable implements IWorkben @ILogService logService: ILogService, @ILanguageModelsService languageModelsService: ILanguageModelsService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, ) { super(); + if (!configurationService.getValue(AgentHostEnabledSettingId)) { + return; + } + // Session list controller const listController = this._register(this._instantiationService.createInstance(AgentHostSessionListController)); this._register(chatSessionsService.registerChatSessionItemController(AGENT_HOST_SESSION_TYPE, listController)); @@ -92,9 +98,14 @@ export class ClaudeAgentHostContribution extends Disposable implements IWorkbenc @ILogService logService: ILogService, @ILanguageModelsService languageModelsService: ILanguageModelsService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, ) { super(); + if (!configurationService.getValue(AgentHostEnabledSettingId)) { + return; + } + // Session list controller const listController = this._register(this._instantiationService.createInstance(AgentHostSessionListController)); this._register(chatSessionsService.registerChatSessionItemController(AGENT_HOST_CLAUDE_SESSION_TYPE, listController)); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHostChatContribution.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHostChatContribution.ts index d9212a6d7e8..4e74f075dc1 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHostChatContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHostChatContribution.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../../base/common/lifecycle.js'; -import { IAgentHostService } from '../../../../../platform/agent/common/agentService.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IAgentHostService, AgentHostEnabledSettingId } from '../../../../../platform/agent/common/agentService.js'; import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; @@ -32,9 +33,14 @@ export class CopilotAgentHostContribution extends Disposable implements IWorkben @ILogService logService: ILogService, @ILanguageModelsService languageModelsService: ILanguageModelsService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, ) { super(); + if (!configurationService.getValue(AgentHostEnabledSettingId)) { + return; + } + // Session list controller const listController = this._register(this._instantiationService.createInstance(AgentHostSessionListController)); this._register(chatSessionsService.registerChatSessionItemController(AGENT_HOST_SESSION_TYPE, listController)); @@ -87,9 +93,14 @@ export class ClaudeAgentHostContribution extends Disposable implements IWorkbenc @ILogService logService: ILogService, @ILanguageModelsService languageModelsService: ILanguageModelsService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, ) { super(); + if (!configurationService.getValue(AgentHostEnabledSettingId)) { + return; + } + // Session list controller const listController = this._register(this._instantiationService.createInstance(AgentHostSessionListController)); this._register(chatSessionsService.registerChatSessionItemController(AGENT_HOST_CLAUDE_SESSION_TYPE, listController)); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c1cb465e781..999a3f325c2 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -8,6 +8,7 @@ import { Disposable, DisposableMap, DisposableStore } from '../../../../base/com import { Schemas } from '../../../../base/common/network.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { PolicyCategory } from '../../../../base/common/policy.js'; +import { AgentHostEnabledSettingId } from '../../../../platform/agent/common/agentService.js'; import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js'; import * as nls from '../../../../nls.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; @@ -690,6 +691,12 @@ configurationRegistry.registerConfiguration({ } } }, + [AgentHostEnabledSettingId]: { + type: 'boolean', + description: nls.localize('chat.agentHost.enabled', "When enabled, the agent host process is spawned to provide Copilot SDK and Claude SDK agents."), + default: false, + tags: ['experimental'], + }, [ChatConfiguration.PlanAgentDefaultModel]: { type: 'string', description: nls.localize('chat.planAgent.defaultModel.description', "Select the default language model to use for the Plan agent from the available providers."), diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts index ee2d5c9cf72..72c3e576540 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts @@ -11,6 +11,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { mock, upcastPartial } from '../../../../../../base/test/common/mock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { ILogService, NullLogService } from '../../../../../../platform/log/common/log.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IAgentHostService, IAgentMessageEvent, IAgentProgressEvent, IAgentSessionMetadata, AgentSession } from '../../../../../../platform/agent/common/agentService.js'; import { IDefaultAccountService } from '../../../../../../platform/defaultAccount/common/defaultAccount.js'; import { IAuthenticationService } from '../../../../../services/authentication/common/authentication.js'; @@ -119,6 +120,7 @@ function createTestServices(disposables: DisposableStore) { deltaLanguageModelChatProviderDescriptors: () => { }, registerLanguageModelProvider: () => toDisposable(() => { }), }); + instantiationService.stub(IConfigurationService, { getValue: () => true }); return { instantiationService, agentHostService, chatAgentService }; }