diff --git a/rollup/plugins/feature-isolation-check-plugin.ts b/rollup/plugins/feature-isolation-check-plugin.ts new file mode 100644 index 00000000..50ad3e23 --- /dev/null +++ b/rollup/plugins/feature-isolation-check-plugin.ts @@ -0,0 +1,92 @@ +import type { Plugin, PluginContext } from 'rollup' + +interface FeatureIsolationCheckOptions { + /** Matches the modules of the optional features (e.g. chat, notebook, mcp) */ + featureModules: RegExp + /** Matches the entry points that are allowed to load feature modules (their service overrides) */ + allowedEntries: RegExp + /** + * Maximum size (in bytes of transformed code) of the feature modules statically reachable from + * the other entry points. It covers the known remaining dependencies, and fails the build when a + * VSCode update (or a change here) makes more of the features load unconditionally + */ + maxSize: number +} + +/** + * Checks that optional features are only loaded by their own service overrides, so that a workbench + * without them doesn't load (and bundle) their implementation + */ +export default ({ + featureModules, + allowedEntries, + maxSize +}: FeatureIsolationCheckOptions): Plugin => { + function getSize(context: PluginContext, id: string) { + return context.getModuleInfo(id)?.code?.length ?? 0 + } + + function collectStaticImports(context: PluginContext, from: string[]) { + const seen = new Set(from) + const queue = [...from] + while (queue.length > 0) { + const id = queue.pop()! + for (const imported of context.getModuleInfo(id)?.importedIds ?? []) { + if (!seen.has(imported)) { + seen.add(imported) + queue.push(imported) + } + } + } + return seen + } + + return { + name: 'feature-isolation-check', + buildEnd(error) { + if (error != null) { + return + } + const entries = Array.from(this.getModuleIds()).filter( + (id) => this.getModuleInfo(id)!.isEntry && !allowedEntries.test(id) + ) + const reachable = collectStaticImports(this, entries) + const reachableFeatureModules = Array.from(reachable).filter((id) => featureModules.test(id)) + const totalSize = reachableFeatureModules.reduce((acc, id) => acc + getSize(this, id), 0) + + // Where the other modules start depending on the features, and how much each of them loads + const crossings = Array.from(reachable) + .filter((id) => !featureModules.test(id)) + .flatMap((importer) => + this.getModuleInfo(importer)! + .importedIds.filter((id) => featureModules.test(id)) + .map((imported) => ({ importer, imported })) + ) + .map(({ importer, imported }) => ({ + importer, + imported, + size: Array.from(collectStaticImports(this, [imported])) + .filter((id) => featureModules.test(id)) + .reduce((acc, id) => acc + getSize(this, id), 0) + })) + .sort((a, b) => b.size - a.size) + + const kb = (size: number) => `${Math.round(size / 1024)}KB` + const shorten = (id: string) => id.replace(/^.*?\/(vs|src)\//, '$1/') + const report = [ + `${reachableFeatureModules.length} feature modules (${kb(totalSize)}) are loaded without their service overrides (max ${kb(maxSize)})`, + ...crossings + .slice(0, 20) + .map( + ({ importer, imported, size }) => + ` ${kb(size).padStart(7)} ${shorten(importer)} -> ${shorten(imported)}` + ) + ].join('\n') + + if (totalSize > maxSize) { + this.error(report) + } + this.info(report) + } + } +} diff --git a/rollup/rollup.config.ts b/rollup/rollup.config.ts index f95d0b63..4acf3065 100644 --- a/rollup/rollup.config.ts +++ b/rollup/rollup.config.ts @@ -20,6 +20,7 @@ import { import vscodeAssetGlobMetaUrl from './plugins/vscode-asset-glob-meta-url-plugin.js' import dynamicImportPolyfillPlugin from './plugins/dynamic-import-polyfill-plugin.js' import resolveAssetUrlPlugin from './plugins/resolve-asset-url-plugin.js' +import featureIsolationCheckPlugin from './plugins/feature-isolation-check-plugin.js' import { typeDedupReplaceTransformer } from './tools/typeDedup.js' import { DIST_DIR_MAIN, @@ -191,6 +192,15 @@ export default (args: Record): rollup.RollupOptions => { return undefined } }, + featureIsolationCheckPlugin({ + // Service identifiers (`.service` files) are shared on purpose + featureModules: /\/vs\/workbench\/contrib\/(chat|notebook|mcp)\/(?!.*\.service\.js$)/, + // Service overrides that build on top of those features + allowedEntries: + /\/src\/service-override\/(chat|notebook|mcp|interactive|ai|speech|welcome)\.ts$/, + // ~910KB with VSCode 1.138: mostly dictation, go to symbol in chat, editor tabs and chat context keys + maxSize: 1000 * 1024 + }), configuredSubpackagePlugin(), copy({ hook: 'writeBundle', diff --git a/scripts/check-unsupported-decorator.ts b/scripts/check-unsupported-decorator.ts index 2c435cdb..c5cd828a 100644 --- a/scripts/check-unsupported-decorator.ts +++ b/scripts/check-unsupported-decorator.ts @@ -1,11 +1,14 @@ import { Project } from 'ts-morph' const project = new Project({ tsConfigFilePath: 'tsconfig.json' }) -const sourceFile = project.getSourceFileOrThrow('src/missing-services.ts') +const sourceFiles = [ + 'src/missing-services.ts', + 'src/service-override/tools/missingMainThreadCustomers.ts' +].map((path) => project.getSourceFileOrThrow(path)) const issues: string[] = [] -for (const cls of sourceFile.getClasses()) { +for (const cls of sourceFiles.flatMap((sourceFile) => sourceFile.getClasses())) { for (const prop of cls.getProperties()) { const initText = prop.getInitializer()?.getText().trim() const usesUnsupported = initText === 'unsupported' @@ -26,4 +29,4 @@ if (issues.length) { console.error(issues.join('\n')) process.exit(1) } -console.log('✅ All consistent') \ No newline at end of file +console.log('✅ All consistent') diff --git a/src/missing-services.ts b/src/missing-services.ts index 2a67f876..5fedfaef 100644 --- a/src/missing-services.ts +++ b/src/missing-services.ts @@ -511,7 +511,7 @@ import { getBuiltInExtensionTranslationsUris, getExtensionIdProvidingCurrentLocale } from './l10n.js' -import { unsupported } from './tools.js' +import { Unsupported, unsupported } from './tools.js' import { NullAgentHostService } from 'vs/platform/agentHost/browser/nullAgentHostService' import { NullSSHRemoteAgentHostService } from 'vs/platform/agentHost/browser/nullSshRemoteAgentHostService' @@ -543,7 +543,7 @@ import { IChatInputNotificationService } from 'vs/workbench/contrib/chat/browser import { IChatPhoneInputPresenter } from 'vs/workbench/contrib/chat/browser/widget/input/chatPhoneInputPresenter.service' import { IChatImageCarouselService } from 'vs/workbench/contrib/chat/browser/chatImageCarouselService.service.js' import { IChatDebugService } from 'vs/workbench/contrib/chat/common/chatDebugService.service.js' -import { createVSCodeHarnessDescriptor } from 'vs/workbench/contrib/chat/common/customizationHarnessService' +import type { IHarnessDescriptor } from 'vs/workbench/contrib/chat/common/customizationHarnessService' import { ICustomizationHarnessService } from 'vs/workbench/contrib/chat/common/customizationHarnessService.service' import type { IEnablementModel } from 'vs/workbench/contrib/chat/common/enablement.js' import { IPluginGitService } from 'vs/workbench/contrib/chat/common/plugins/pluginGitService.service.js' @@ -583,29 +583,6 @@ import { IAgentHostCustomizationService } from 'vs/workbench/contrib/chat/browse import { IAgentHostActiveClientService } from 'vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostActiveClientService.service' import { IGitHubService } from 'vs/platform/github/common/githubService.service.js' -function Unsupported(target: object, propertyKey: string, descriptor?: PropertyDescriptor) { - function unsupported() { - throw new Error( - `Unsupported: ${target.constructor.name}.${propertyKey} is not supported. You are using a feature without registering the corresponding service override.` - ) - } - if (descriptor != null) { - if (descriptor.value != null) { - descriptor.value = unsupported - } else if (descriptor.get != null) { - descriptor.get = unsupported - } - } else { - Object.defineProperty(target, propertyKey, { - get() { - unsupported() - }, - set() {}, - configurable: true, - enumerable: true - }) - } -} /** * Editor services: all editor service that are not defined in vs/editor/standalone/ */ @@ -7270,8 +7247,15 @@ class CustomizationHarnessService implements ICustomizationHarnessService { URI.parse('chat-session://local/default') ) activeHarness: ICustomizationHarnessService['activeHarness'] = constObservable(SessionType.Local) - availableHarnesses: ICustomizationHarnessService['availableHarnesses'] = constObservable([ - createVSCodeHarnessDescriptor() + // Not using `createVSCodeHarnessDescriptor` on purpose: importing it loads most of the chat implementation + availableHarnesses: ICustomizationHarnessService['availableHarnesses'] = constObservable< + IHarnessDescriptor[] + >([ + { + id: SessionType.Local, + label: 'Local', + icon: Codicon.vm + } ]) onDidChangeSlashCommands: ICustomizationHarnessService['onDidChangeSlashCommands'] = Event.None onDidChangeCustomAgents: ICustomizationHarnessService['onDidChangeCustomAgents'] = Event.None diff --git a/src/service-override/chat.ts b/src/service-override/chat.ts index f5a22709..b046dc11 100644 --- a/src/service-override/chat.ts +++ b/src/service-override/chat.ts @@ -262,6 +262,7 @@ import 'vs/workbench/contrib/chat/browser/agentSessions/experiments/agentSession import 'vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagement.contribution' import 'vs/workbench/contrib/chat/browser/attachments/chatReferenceAttachmentWidget.contribution' import 'vs/workbench/contrib/chat/browser/attachments/transcriptContextAttachmentWidget.contribution' +import 'vs/workbench/api/browser/extensionHost.chat.contribution' class DefaultAccountService implements IDefaultAccountService { declare _serviceBrand: undefined diff --git a/src/service-override/extensions.ts b/src/service-override/extensions.ts index ad4cc155..868bad80 100644 --- a/src/service-override/extensions.ts +++ b/src/service-override/extensions.ts @@ -69,7 +69,8 @@ import { } from 'vs/platform/extensionManagement/common/extensionsScannerService.js' import * as platform from 'vs/base/common/platform' import { getBuiltInExtensionTranslationsUris } from '../l10n.js' -import 'vs/workbench/api/browser/extensionHost.contribution' +import 'vs/workbench/api/browser/extensionHost.base.contribution' +import './tools/missingMainThreadCustomers.js' export interface WorkerConfig { url: string diff --git a/src/service-override/mcp.ts b/src/service-override/mcp.ts index b207c864..615935b5 100644 --- a/src/service-override/mcp.ts +++ b/src/service-override/mcp.ts @@ -36,6 +36,7 @@ import { IWorkbenchMcpGatewayService } from 'vs/workbench/contrib/mcp/common/mcp import { BrowserMcpGatewayService } from 'vs/workbench/contrib/mcp/browser/mcpGatewayService' import 'vs/workbench/contrib/mcp/browser/mcp.contribution' import 'vs/workbench/contrib/mcp/browser/mcp.view.contribution' +import 'vs/workbench/api/browser/extensionHost.mcp.contribution' export default function getServiceOverride(): IEditorOverrideServices { return { diff --git a/src/service-override/notebook.ts b/src/service-override/notebook.ts index 5562b461..d797fc75 100644 --- a/src/service-override/notebook.ts +++ b/src/service-override/notebook.ts @@ -40,6 +40,7 @@ import { NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/brows import { INotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.service' import 'vs/workbench/contrib/replNotebook/browser/repl.contribution' import 'vs/workbench/contrib/notebook/browser/notebook.contribution' +import 'vs/workbench/api/browser/extensionHost.notebook.contribution' export default function getServiceOverride(): IEditorOverrideServices { return { diff --git a/src/service-override/tools/missingMainThreadCustomers.ts b/src/service-override/tools/missingMainThreadCustomers.ts new file mode 100644 index 00000000..1c195770 --- /dev/null +++ b/src/service-override/tools/missingMainThreadCustomers.ts @@ -0,0 +1,291 @@ +import type { IDisposable } from 'vs/base/common/lifecycle' +import { + MainContext, + type MainThreadChatAgentsShape2, + type MainThreadChatContextShape, + type MainThreadChatDebugShape, + type MainThreadChatInputNotificationShape, + type MainThreadChatQuotaShape, + type MainThreadChatSessionsShape, + type MainThreadChatStatusShape, + type MainThreadCodeMapperShape, + type MainThreadLanguageModelToolsShape, + type MainThreadLanguageModelsShape, + type MainThreadMcpShape, + type MainThreadNotebookDocumentsShape, + type MainThreadNotebookEditorsShape, + type MainThreadNotebookKernelsShape, + type MainThreadNotebookRenderersShape, + type MainThreadNotebookShape +} from 'vs/workbench/api/common/extHost.protocol' +import { + extHostCustomer, + type IExtHostContext +} from 'vs/workbench/services/extensions/common/extHostCustomers' +import type { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier' +import { noop, Unsupported, unsupported } from '../../tools.js' + +// Main thread counterparts of the extension host API of optional features (chat, language models, notebooks, mcp) +// Their actual implementations are only registered by the corresponding service overrides, but the extension host +// requires all of them to be registered, and some of them are called when the extension host starts +// (e.g. `ExtHostLanguageModelTools` calls `$getTools`) + +class MissingMainThreadLanguageModels implements MainThreadLanguageModelsShape { + $registerLanguageModelProvider: MainThreadLanguageModelsShape['$registerLanguageModelProvider'] = + noop + $onLMProviderChange: MainThreadLanguageModelsShape['$onLMProviderChange'] = noop + $unregisterProvider: MainThreadLanguageModelsShape['$unregisterProvider'] = noop + @Unsupported + $tryStartChatRequest: MainThreadLanguageModelsShape['$tryStartChatRequest'] = unsupported + $reportResponsePart: MainThreadLanguageModelsShape['$reportResponsePart'] = async () => {} + $reportResponseDone: MainThreadLanguageModelsShape['$reportResponseDone'] = async () => {} + $selectChatModels: MainThreadLanguageModelsShape['$selectChatModels'] = async () => [] + @Unsupported + $countTokens: MainThreadLanguageModelsShape['$countTokens'] = unsupported + $cancelLanguageModelChatRequest: MainThreadLanguageModelsShape['$cancelLanguageModelChatRequest'] = + noop + $fileIsIgnored: MainThreadLanguageModelsShape['$fileIsIgnored'] = async () => false + $registerFileIgnoreProvider: MainThreadLanguageModelsShape['$registerFileIgnoreProvider'] = noop + $unregisterFileIgnoreProvider: MainThreadLanguageModelsShape['$unregisterFileIgnoreProvider'] = + noop + dispose(): void {} +} + +class MissingMainThreadChatAgents2 implements MainThreadChatAgentsShape2 { + $handleProgressChunk: MainThreadChatAgentsShape2['$handleProgressChunk'] = async () => {} + $handleAnchorResolve: MainThreadChatAgentsShape2['$handleAnchorResolve'] = noop + $registerAgent: MainThreadChatAgentsShape2['$registerAgent'] = noop + $registerChatParticipantDetectionProvider: MainThreadChatAgentsShape2['$registerChatParticipantDetectionProvider'] = + noop + $unregisterChatParticipantDetectionProvider: MainThreadChatAgentsShape2['$unregisterChatParticipantDetectionProvider'] = + noop + $registerPromptFileProvider: MainThreadChatAgentsShape2['$registerPromptFileProvider'] = noop + $unregisterPromptFileProvider: MainThreadChatAgentsShape2['$unregisterPromptFileProvider'] = noop + $onDidChangePromptFiles: MainThreadChatAgentsShape2['$onDidChangePromptFiles'] = noop + $registerChatSessionCustomizationProvider: MainThreadChatAgentsShape2['$registerChatSessionCustomizationProvider'] = + noop + $unregisterChatSessionCustomizationProvider: MainThreadChatAgentsShape2['$unregisterChatSessionCustomizationProvider'] = + noop + $onDidChangeCustomizations: MainThreadChatAgentsShape2['$onDidChangeCustomizations'] = noop + $registerAgentCompletionsProvider: MainThreadChatAgentsShape2['$registerAgentCompletionsProvider'] = + noop + $unregisterAgentCompletionsProvider: MainThreadChatAgentsShape2['$unregisterAgentCompletionsProvider'] = + noop + $updateAgent: MainThreadChatAgentsShape2['$updateAgent'] = noop + $unregisterAgent: MainThreadChatAgentsShape2['$unregisterAgent'] = noop + @Unsupported + $transferActiveChatSession: MainThreadChatAgentsShape2['$transferActiveChatSession'] = unsupported + $provideCustomAgents: MainThreadChatAgentsShape2['$provideCustomAgents'] = async () => [] + $provideInstructions: MainThreadChatAgentsShape2['$provideInstructions'] = async () => [] + $provideSkills: MainThreadChatAgentsShape2['$provideSkills'] = async () => [] + $provideSlashCommands: MainThreadChatAgentsShape2['$provideSlashCommands'] = async () => [] + $provideHooks: MainThreadChatAgentsShape2['$provideHooks'] = async () => [] + $providePlugins: MainThreadChatAgentsShape2['$providePlugins'] = async () => [] + dispose(): void {} +} + +class MissingMainThreadCodeMapper implements MainThreadCodeMapperShape { + $registerCodeMapperProvider: MainThreadCodeMapperShape['$registerCodeMapperProvider'] = noop + $unregisterCodeMapperProvider: MainThreadCodeMapperShape['$unregisterCodeMapperProvider'] = noop + $handleProgress: MainThreadCodeMapperShape['$handleProgress'] = async () => {} + dispose(): void {} +} + +class MissingMainThreadLanguageModelTools implements MainThreadLanguageModelToolsShape { + $getTools: MainThreadLanguageModelToolsShape['$getTools'] = async () => [] + $acceptToolProgress: MainThreadLanguageModelToolsShape['$acceptToolProgress'] = noop + @Unsupported + $invokeTool: MainThreadLanguageModelToolsShape['$invokeTool'] = unsupported + @Unsupported + $countTokensForInvocation: MainThreadLanguageModelToolsShape['$countTokensForInvocation'] = + unsupported + $registerTool: MainThreadLanguageModelToolsShape['$registerTool'] = noop + $registerToolWithDefinition: MainThreadLanguageModelToolsShape['$registerToolWithDefinition'] = + noop + $unregisterTool: MainThreadLanguageModelToolsShape['$unregisterTool'] = noop + dispose(): void {} +} + +class MissingMainThreadChatContext implements MainThreadChatContextShape { + $registerChatWorkspaceContextProvider: MainThreadChatContextShape['$registerChatWorkspaceContextProvider'] = + noop + $registerChatExplicitContextProvider: MainThreadChatContextShape['$registerChatExplicitContextProvider'] = + noop + $registerChatResourceContextProvider: MainThreadChatContextShape['$registerChatResourceContextProvider'] = + noop + $unregisterChatContextProvider: MainThreadChatContextShape['$unregisterChatContextProvider'] = + noop + $updateWorkspaceContextItems: MainThreadChatContextShape['$updateWorkspaceContextItems'] = noop + @Unsupported + $executeChatContextItemCommand: MainThreadChatContextShape['$executeChatContextItemCommand'] = + unsupported + dispose(): void {} +} + +class MissingMainThreadChatDebug implements MainThreadChatDebugShape { + $registerChatDebugLogProvider: MainThreadChatDebugShape['$registerChatDebugLogProvider'] = noop + $unregisterChatDebugLogProvider: MainThreadChatDebugShape['$unregisterChatDebugLogProvider'] = + noop + $acceptChatDebugEvent: MainThreadChatDebugShape['$acceptChatDebugEvent'] = noop + $subscribeToCoreDebugEvents: MainThreadChatDebugShape['$subscribeToCoreDebugEvents'] = noop + $unsubscribeFromCoreDebugEvents: MainThreadChatDebugShape['$unsubscribeFromCoreDebugEvents'] = + noop + dispose(): void {} +} + +class MissingMainThreadChatStatus implements MainThreadChatStatusShape { + $setEntry: MainThreadChatStatusShape['$setEntry'] = noop + $disposeEntry: MainThreadChatStatusShape['$disposeEntry'] = noop + dispose(): void {} +} + +class MissingMainThreadChatQuota implements MainThreadChatQuotaShape { + $updateQuotas: MainThreadChatQuotaShape['$updateQuotas'] = noop + dispose(): void {} +} + +class MissingMainThreadChatInputNotification implements MainThreadChatInputNotificationShape { + $setNotification: MainThreadChatInputNotificationShape['$setNotification'] = noop + $disposeNotification: MainThreadChatInputNotificationShape['$disposeNotification'] = noop + dispose(): void {} +} + +class MissingMainThreadChatSessions implements MainThreadChatSessionsShape { + $registerChatSessionItemController: MainThreadChatSessionsShape['$registerChatSessionItemController'] = + noop + $updateChatSessionItemControllerCapabilities: MainThreadChatSessionsShape['$updateChatSessionItemControllerCapabilities'] = + noop + $unregisterChatSessionItemController: MainThreadChatSessionsShape['$unregisterChatSessionItemController'] = + noop + $updateChatSessionItems: MainThreadChatSessionsShape['$updateChatSessionItems'] = async () => {} + $addOrUpdateChatSessionItem: MainThreadChatSessionsShape['$addOrUpdateChatSessionItem'] = + async () => {} + $onDidCommitChatSessionItem: MainThreadChatSessionsShape['$onDidCommitChatSessionItem'] = noop + $registerChatSessionContentProvider: MainThreadChatSessionsShape['$registerChatSessionContentProvider'] = + noop + $unregisterChatSessionContentProvider: MainThreadChatSessionsShape['$unregisterChatSessionContentProvider'] = + noop + $onDidChangeChatSessionOptions: MainThreadChatSessionsShape['$onDidChangeChatSessionOptions'] = + noop + $onDidChangeChatSessionProviderOptions: MainThreadChatSessionsShape['$onDidChangeChatSessionProviderOptions'] = + noop + $updateChatSessionInputState: MainThreadChatSessionsShape['$updateChatSessionInputState'] = noop + $handleProgressChunk: MainThreadChatSessionsShape['$handleProgressChunk'] = async () => {} + $handleAnchorResolve: MainThreadChatSessionsShape['$handleAnchorResolve'] = noop + $handleProgressComplete: MainThreadChatSessionsShape['$handleProgressComplete'] = noop + dispose(): void {} +} + +class MissingMainThreadNotebook implements MainThreadNotebookShape { + $registerNotebookSerializer: MainThreadNotebookShape['$registerNotebookSerializer'] = noop + $unregisterNotebookSerializer: MainThreadNotebookShape['$unregisterNotebookSerializer'] = noop + $registerNotebookCellStatusBarItemProvider: MainThreadNotebookShape['$registerNotebookCellStatusBarItemProvider'] = + async () => {} + $unregisterNotebookCellStatusBarItemProvider: MainThreadNotebookShape['$unregisterNotebookCellStatusBarItemProvider'] = + async () => {} + $emitCellStatusBarEvent: MainThreadNotebookShape['$emitCellStatusBarEvent'] = noop + dispose(): void {} +} + +class MissingMainThreadNotebookKernels implements MainThreadNotebookKernelsShape { + $postMessage: MainThreadNotebookKernelsShape['$postMessage'] = async () => false + $addKernel: MainThreadNotebookKernelsShape['$addKernel'] = async () => {} + $updateKernel: MainThreadNotebookKernelsShape['$updateKernel'] = noop + $removeKernel: MainThreadNotebookKernelsShape['$removeKernel'] = noop + $updateNotebookPriority: MainThreadNotebookKernelsShape['$updateNotebookPriority'] = noop + $createExecution: MainThreadNotebookKernelsShape['$createExecution'] = noop + $updateExecution: MainThreadNotebookKernelsShape['$updateExecution'] = noop + $completeExecution: MainThreadNotebookKernelsShape['$completeExecution'] = noop + $createNotebookExecution: MainThreadNotebookKernelsShape['$createNotebookExecution'] = noop + $beginNotebookExecution: MainThreadNotebookKernelsShape['$beginNotebookExecution'] = noop + $completeNotebookExecution: MainThreadNotebookKernelsShape['$completeNotebookExecution'] = noop + $addKernelDetectionTask: MainThreadNotebookKernelsShape['$addKernelDetectionTask'] = + async () => {} + $removeKernelDetectionTask: MainThreadNotebookKernelsShape['$removeKernelDetectionTask'] = noop + $addKernelSourceActionProvider: MainThreadNotebookKernelsShape['$addKernelSourceActionProvider'] = + async () => {} + $removeKernelSourceActionProvider: MainThreadNotebookKernelsShape['$removeKernelSourceActionProvider'] = + noop + $emitNotebookKernelSourceActionsChangeEvent: MainThreadNotebookKernelsShape['$emitNotebookKernelSourceActionsChangeEvent'] = + noop + $receiveVariable: MainThreadNotebookKernelsShape['$receiveVariable'] = noop + $variablesUpdated: MainThreadNotebookKernelsShape['$variablesUpdated'] = noop + dispose(): void {} +} + +class MissingMainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape { + @Unsupported + $tryCreateNotebook: MainThreadNotebookDocumentsShape['$tryCreateNotebook'] = unsupported + @Unsupported + $tryOpenNotebook: MainThreadNotebookDocumentsShape['$tryOpenNotebook'] = unsupported + @Unsupported + $trySaveNotebook: MainThreadNotebookDocumentsShape['$trySaveNotebook'] = unsupported + dispose(): void {} +} + +class MissingMainThreadNotebookEditors implements MainThreadNotebookEditorsShape { + @Unsupported + $tryShowNotebookDocument: MainThreadNotebookEditorsShape['$tryShowNotebookDocument'] = unsupported + @Unsupported + $tryRevealRange: MainThreadNotebookEditorsShape['$tryRevealRange'] = unsupported + $trySetSelections: MainThreadNotebookEditorsShape['$trySetSelections'] = noop + dispose(): void {} +} + +class MissingMainThreadNotebookRenderers implements MainThreadNotebookRenderersShape { + $postMessage: MainThreadNotebookRenderersShape['$postMessage'] = async () => false + dispose(): void {} +} + +class MissingMainThreadMcp implements MainThreadMcpShape { + $onDidChangeState: MainThreadMcpShape['$onDidChangeState'] = noop + $onDidPublishLog: MainThreadMcpShape['$onDidPublishLog'] = noop + $onDidReceiveMessage: MainThreadMcpShape['$onDidReceiveMessage'] = noop + $upsertMcpCollection: MainThreadMcpShape['$upsertMcpCollection'] = noop + $deleteMcpCollection: MainThreadMcpShape['$deleteMcpCollection'] = noop + @Unsupported + $getTokenFromServerMetadata: MainThreadMcpShape['$getTokenFromServerMetadata'] = unsupported + @Unsupported + $getTokenForProviderId: MainThreadMcpShape['$getTokenForProviderId'] = unsupported + $logMcpAuthSetup: MainThreadMcpShape['$logMcpAuthSetup'] = noop + @Unsupported + $startMcpGateway: MainThreadMcpShape['$startMcpGateway'] = unsupported + $disposeMcpGateway: MainThreadMcpShape['$disposeMcpGateway'] = noop + dispose(): void {} +} + +const missingMainThreadCustomers: [ProxyIdentifier, new () => IDisposable][] = [ + [MainContext.MainThreadLanguageModels, MissingMainThreadLanguageModels], + [MainContext.MainThreadChatAgents2, MissingMainThreadChatAgents2], + [MainContext.MainThreadCodeMapper, MissingMainThreadCodeMapper], + [MainContext.MainThreadLanguageModelTools, MissingMainThreadLanguageModelTools], + [MainContext.MainThreadChatContext, MissingMainThreadChatContext], + [MainContext.MainThreadChatDebug, MissingMainThreadChatDebug], + [MainContext.MainThreadChatStatus, MissingMainThreadChatStatus], + [MainContext.MainThreadChatQuota, MissingMainThreadChatQuota], + [MainContext.MainThreadChatInputNotification, MissingMainThreadChatInputNotification], + [MainContext.MainThreadChatSessions, MissingMainThreadChatSessions], + [MainContext.MainThreadNotebook, MissingMainThreadNotebook], + [MainContext.MainThreadNotebookKernels, MissingMainThreadNotebookKernels], + [MainContext.MainThreadNotebookDocuments, MissingMainThreadNotebookDocuments], + [MainContext.MainThreadNotebookEditors, MissingMainThreadNotebookEditors], + [MainContext.MainThreadNotebookRenderers, MissingMainThreadNotebookRenderers], + [MainContext.MainThreadMcp, MissingMainThreadMcp] +] + +// Unnamed customers are instantiated after the named ones, and a real implementation set later replaces the missing one, +// so it only registers the missing implementations whatever the import order is +class MissingMainThreadCustomers implements IDisposable { + constructor(extHostContext: IExtHostContext) { + for (const [id, ctor] of missingMainThreadCustomers) { + try { + extHostContext.assertRegistered([id]) + } catch { + extHostContext.set(id, new ctor()) + } + } + } + + dispose(): void {} +} +extHostCustomer(MissingMainThreadCustomers) diff --git a/src/tools.ts b/src/tools.ts index 4a84e67a..04f1072f 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -4,6 +4,33 @@ export function unsupported(): never { export const noop = (): void => {} +/** + * Makes a property, method or getter of a missing implementation throw an explicit error + */ +export function Unsupported(target: object, propertyKey: string, descriptor?: PropertyDescriptor) { + function unsupported() { + throw new Error( + `Unsupported: ${target.constructor.name}.${propertyKey} is not supported. You are using a feature without registering the corresponding service override.` + ) + } + if (descriptor != null) { + if (descriptor.value != null) { + descriptor.value = unsupported + } else if (descriptor.get != null) { + descriptor.get = unsupported + } + } else { + Object.defineProperty(target, propertyKey, { + get() { + unsupported() + }, + set() {}, + configurable: true, + enumerable: true + }) + } +} + export function memoized(fct: (...args: A) => T): (...args: A) => T { let v: T | null = null return (...args) => { diff --git a/vscode-patches/0109-refactor-avoid-runtime-dependencies-from-core-on-cha.patch b/vscode-patches/0109-refactor-avoid-runtime-dependencies-from-core-on-cha.patch new file mode 100644 index 00000000..6e061657 --- /dev/null +++ b/vscode-patches/0109-refactor-avoid-runtime-dependencies-from-core-on-cha.patch @@ -0,0 +1,383 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brian Jenkins +Date: Mon, 28 Sep 2026 00:29:33 -0400 +Subject: [PATCH] refactor: avoid runtime dependencies from core on chat and + notebook implementations + +Search, terminal decorations, SCM, tasks, getting started and the extension +host only needed a type, a lazily-used class, a command/tool id or a small +helper from chat/notebook, but imported them from modules that load the whole +chat implementation or notebook editor, which made them part of every +workbench, even without the chat/notebook service overrides. + +Co-Authored-By: Claude Opus 5.5 +--- + .../api/common/extHostLanguageModelTools.ts | 3 +- + .../api/common/extHostTypeConverters.ts | 2 +- + .../chat/browser/actions/chatConstants.ts | 1 + + .../contrib/chat/common/chatImageBuffer.ts | 34 +++++++++++++++++++ + .../chat/common/chatImageExtraction.ts | 31 ++--------------- + .../common/tools/builtinTools/editFileTool.ts | 5 +-- + .../chat/common/tools/builtinTools/toolIds.ts | 9 +++++ + .../chat/common/tools/builtinTools/tools.ts | 2 +- + .../contrib/scm/browser/scm.contribution.ts | 2 +- + .../workbench/contrib/scm/browser/scmInput.ts | 2 +- + .../browser/searchTreeModel/searchResult.ts | 8 ++--- + .../contrib/search/browser/searchView.ts | 9 +++-- + .../tasks/browser/abstractTaskService.ts | 2 +- + .../terminal/browser/xterm/decorationAddon.ts | 4 ++- + .../browser/agentSessionsWelcome.ts | 4 +-- + .../browser/agentSessionsWelcomeInput.ts | 1 + + .../browser/gettingStarted.contribution.ts | 4 +-- + 17 files changed, 74 insertions(+), 49 deletions(-) + create mode 100644 src/vs/workbench/contrib/chat/common/chatImageBuffer.ts + create mode 100644 src/vs/workbench/contrib/chat/common/tools/builtinTools/toolIds.ts + +diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts +index f34d76c9..782d83e6 100644 +--- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts ++++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts +@@ -13,8 +13,7 @@ import { generateUuid } from '../../../base/common/uuid.js'; + import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; + import { IPreparedToolInvocation, IStreamedToolInvocation, isToolInvocationContext, IToolInvocation, IToolInvocationContext, IToolInvocationPreparationContext, IToolInvocationStreamContext, IToolResult, ToolInvocationPresentation } from '../../contrib/chat/common/tools/languageModelToolsService.js'; + import { computeCombinationKey } from '../../contrib/chat/common/tools/languageModelToolsConfirmationService.js'; +-import { ExtensionEditToolId, InternalEditToolId } from '../../contrib/chat/common/tools/builtinTools/editFileTool.js'; +-import { InternalFetchWebPageToolId } from '../../contrib/chat/common/tools/builtinTools/tools.js'; ++import { ExtensionEditToolId, InternalEditToolId, InternalFetchWebPageToolId } from '../../contrib/chat/common/tools/builtinTools/toolIds.js'; + import { SearchExtensionsToolId } from '../../contrib/extensions/common/searchExtensionsTool.js'; + import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; + import { Dto, SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js'; +diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts +index b3e6860b..36aa3bf9 100644 +--- a/src/vs/workbench/api/common/extHostTypeConverters.ts ++++ b/src/vs/workbench/api/common/extHostTypeConverters.ts +@@ -46,7 +46,7 @@ import { IChatRequestModeInstructions } from '../../contrib/chat/common/model/ch + import { IChatAgentMarkdownContentWithVulnerability, IChatAutoModeResolutionPart, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatExtensionsContent, IChatExternalToolInvocationUpdate, IChatFollowup, IChatHookPart, IChatMarkdownContent, IChatMoveMessage, IChatMultiDiffDataSerialized, IChatProgressMessage, IChatPullRequestContent, IChatQuestionCarousel, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTerminalToolInvocationData, IChatTextEdit, IChatThinkingPart, IChatToolInvocationSerialized, IChatTreeData, IChatUserActionEvent, IChatVoiceProgressPart, IChatWarningMessage, IChatInfoMessage, IChatWorkspaceEdit } from '../../contrib/chat/common/chatService/chatService.js'; + import { LocalChatSessionUri } from '../../contrib/chat/common/model/chatUri.js'; + import { ChatRequestToolReferenceEntry, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry } from '../../contrib/chat/common/attachments/chatVariableEntries.js'; +-import { coerceImageBuffer } from '../../contrib/chat/common/chatImageExtraction.js'; ++import { coerceImageBuffer } from '../../contrib/chat/common/chatImageBuffer.js'; + import { ChatSessionStatus, IChatSessionItem } from '../../contrib/chat/common/chatSessionsService.js'; + import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; + import { ChatRequestHooks, resolveEffectiveCommand } from '../../contrib/chat/common/promptSyntax/hookSchema.js'; +diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatConstants.ts b/src/vs/workbench/contrib/chat/browser/actions/chatConstants.ts +index 17e137b5..71002004 100644 +--- a/src/vs/workbench/contrib/chat/browser/actions/chatConstants.ts ++++ b/src/vs/workbench/contrib/chat/browser/actions/chatConstants.ts +@@ -6,6 +6,7 @@ import { localize2 } from '../../../../../nls.js'; + + export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); + export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; ++export const CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID = 'workbench.action.chat.triggerSetupSupportAnonymousAction'; + export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle'; + export const AcceptToolConfirmationActionId = 'workbench.action.chat.acceptTool'; + export const SkipToolConfirmationActionId = 'workbench.action.chat.skipTool'; +diff --git a/src/vs/workbench/contrib/chat/common/chatImageBuffer.ts b/src/vs/workbench/contrib/chat/common/chatImageBuffer.ts +new file mode 100644 +index 00000000..4d0b845d +--- /dev/null ++++ b/src/vs/workbench/contrib/chat/common/chatImageBuffer.ts +@@ -0,0 +1,34 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright (c) Microsoft Corporation. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++// Kept in its own dependency-free module so that code outside of chat (e.g. extHostTypeConverters) ++// can use it without loading the chat model ++export function coerceImageBuffer(value: unknown): Uint8Array | undefined { ++ if (value instanceof Uint8Array) { ++ return value; ++ } ++ if (value instanceof ArrayBuffer) { ++ return new Uint8Array(value); ++ } ++ if (!value || typeof value !== 'object' || Array.isArray(value)) { ++ return undefined; ++ } ++ ++ const record = value as Record; ++ const keys = Object.keys(record).sort((a, b) => Number(a) - Number(b)); ++ if (keys.length === 0) { ++ return undefined; ++ } ++ ++ const result = new Uint8Array(keys.length); ++ for (let index = 0; index < keys.length; index++) { ++ const byte = record[keys[index]]; ++ if (keys[index] !== String(index) || typeof byte !== 'number' || !Number.isInteger(byte) || byte < 0 || byte > 255) { ++ return undefined; ++ } ++ result[index] = byte; ++ } ++ return result; ++} +diff --git a/src/vs/workbench/contrib/chat/common/chatImageExtraction.ts b/src/vs/workbench/contrib/chat/common/chatImageExtraction.ts +index cc289ef3..d0ae0f2d 100644 +--- a/src/vs/workbench/contrib/chat/common/chatImageExtraction.ts ++++ b/src/vs/workbench/contrib/chat/common/chatImageExtraction.ts +@@ -14,6 +14,9 @@ import { ChatResponseResource } from './model/chatModel.js'; + import { IChatContentInlineReference, IChatToolInvocation, IChatToolInvocationSerialized, IToolResultOutputDetailsSerialized } from './chatService/chatService.js'; + import { isToolResultInputOutputDetails, isToolResultOutputDetails, IToolResultOutputDetails } from './tools/languageModelToolsService.js'; + import { getExplicitFileOrImageAttachmentSummary, type IChatRequestVariableEntry, isImageVariableEntry } from './attachments/chatVariableEntries.js'; ++import { coerceImageBuffer } from './chatImageBuffer.js'; ++ ++export { coerceImageBuffer }; + + export interface IChatExtractedImage { + readonly id: string; +@@ -188,34 +191,6 @@ async function extractImageFromInlineReference( + }; + } + +-export function coerceImageBuffer(value: unknown): Uint8Array | undefined { +- if (value instanceof Uint8Array) { +- return value; +- } +- if (value instanceof ArrayBuffer) { +- return new Uint8Array(value); +- } +- if (!value || typeof value !== 'object' || Array.isArray(value)) { +- return undefined; +- } +- +- const record = value as Record; +- const keys = Object.keys(record).sort((a, b) => Number(a) - Number(b)); +- if (keys.length === 0) { +- return undefined; +- } +- +- const result = new Uint8Array(keys.length); +- for (let index = 0; index < keys.length; index++) { +- const byte = record[keys[index]]; +- if (keys[index] !== String(index) || typeof byte !== 'number' || !Number.isInteger(byte) || byte < 0 || byte > 255) { +- return undefined; +- } +- result[index] = byte; +- } +- return result; +-} +- + /** + * Extract images from a chat request's variable attachments (user-attached images). + */ +diff --git a/src/vs/workbench/contrib/chat/common/tools/builtinTools/editFileTool.ts b/src/vs/workbench/contrib/chat/common/tools/builtinTools/editFileTool.ts +index addfc96f..fc33b652 100644 +--- a/src/vs/workbench/contrib/chat/common/tools/builtinTools/editFileTool.ts ++++ b/src/vs/workbench/contrib/chat/common/tools/builtinTools/editFileTool.ts +@@ -15,9 +15,10 @@ import { ICodeMapperService } from '../../editing/chatCodeMapperService.js'; + import { ChatModel } from '../../model/chatModel.js'; + import { IChatService } from '../../chatService/chatService.js'; + import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../languageModelToolsService.js'; ++import { InternalEditToolId } from './toolIds.js'; ++ ++export { ExtensionEditToolId, InternalEditToolId } from './toolIds.js'; + +-export const ExtensionEditToolId = 'vscode_editFile'; +-export const InternalEditToolId = 'vscode_editFile_internal'; + export const EditToolData: IToolData = { + id: InternalEditToolId, + displayName: '', // not used +diff --git a/src/vs/workbench/contrib/chat/common/tools/builtinTools/toolIds.ts b/src/vs/workbench/contrib/chat/common/tools/builtinTools/toolIds.ts +new file mode 100644 +index 00000000..91daae1f +--- /dev/null ++++ b/src/vs/workbench/contrib/chat/common/tools/builtinTools/toolIds.ts +@@ -0,0 +1,9 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright (c) Microsoft Corporation. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++// Kept in a dependency-free module so that the extension host can use them without loading the tools ++export const ExtensionEditToolId = 'vscode_editFile'; ++export const InternalEditToolId = 'vscode_editFile_internal'; ++export const InternalFetchWebPageToolId = 'vscode_fetchWebPage_internal'; +diff --git a/src/vs/workbench/contrib/chat/common/tools/builtinTools/tools.ts b/src/vs/workbench/contrib/chat/common/tools/builtinTools/tools.ts +index 4a711c40..bec1c893 100644 +--- a/src/vs/workbench/contrib/chat/common/tools/builtinTools/tools.ts ++++ b/src/vs/workbench/contrib/chat/common/tools/builtinTools/tools.ts +@@ -104,4 +104,4 @@ export class BuiltinToolsContribution extends Disposable implements IWorkbenchCo + } + } + +-export const InternalFetchWebPageToolId = 'vscode_fetchWebPage_internal'; ++export { InternalFetchWebPageToolId } from './toolIds.js'; +diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +index 6ee2a32c..a5cba418 100644 +--- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts ++++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +@@ -40,7 +40,7 @@ import { SCMAccessibilityHelp } from './scmAccessibilityHelp.js'; + import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; + import { SCMHistoryItemContextContribution } from './scmHistoryChatContext.js'; + import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js'; +-import { CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; ++import { CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../../chat/browser/actions/chatConstants.js'; + import { SCMInputContextKeys } from './scmInput.js'; + import product from '../../../../platform/product/common/product.js'; + +diff --git a/src/vs/workbench/contrib/scm/browser/scmInput.ts b/src/vs/workbench/contrib/scm/browser/scmInput.ts +index 63642985..66846d74 100644 +--- a/src/vs/workbench/contrib/scm/browser/scmInput.ts ++++ b/src/vs/workbench/contrib/scm/browser/scmInput.ts +@@ -70,7 +70,7 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common + import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; + import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js'; + import product from '../../../../platform/product/common/product.js'; +-import { CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; ++import { CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../../chat/browser/actions/chatConstants.js'; + + export const SCMInputContextKeys = { + SCMInputHasValidationMessage: new RawContextKey('scmInputHasValidationMessage', false), +diff --git a/src/vs/workbench/contrib/search/browser/searchTreeModel/searchResult.ts b/src/vs/workbench/contrib/search/browser/searchTreeModel/searchResult.ts +index c55c9aa6..7cba78cd 100644 +--- a/src/vs/workbench/contrib/search/browser/searchTreeModel/searchResult.ts ++++ b/src/vs/workbench/contrib/search/browser/searchTreeModel/searchResult.ts +@@ -10,7 +10,7 @@ import { ITextModel } from '../../../../../editor/common/model.js'; + import { IModelService } from '../../../../../editor/common/services/model.js'; + import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; + import { IProgress, IProgressStep } from '../../../../../platform/progress/common/progress.js'; +-import { NotebookEditorWidget } from '../../../notebook/browser/notebookEditorWidget.js'; ++import type { NotebookEditorWidget } from '../../../notebook/browser/notebookEditorWidget.js'; + import { INotebookEditorService } from '../../../notebook/browser/services/notebookEditorService.js'; + import { IAITextQuery, IFileMatch, ISearchComplete, ITextQuery, QueryType } from '../../../../services/search/common/search.js'; + import { arrayContainsElementOrParent, IChangeEvent, ISearchTreeFileMatch, ISearchTreeFolderMatch, IPlainTextSearchHeading, ISearchModel, ISearchResult, isSearchTreeFileMatch, isSearchTreeFolderMatch, isSearchTreeFolderMatchNoRoot, isSearchTreeFolderMatchWithResource, isSearchTreeMatch, isTextSearchHeading, ITextSearchHeading, mergeSearchResultEvents, RenderableMatch, SEARCH_RESULT_PREFIX } from './searchTreeCommon.js'; +@@ -48,9 +48,9 @@ export class SearchResultImpl extends Disposable implements ISearchResult { + this._register(this.modelService.onModelAdded(model => this.onModelAdded(model))); + + this._register(this.notebookEditorService.onDidAddNotebookEditor(widget => { +- if (widget instanceof NotebookEditorWidget) { +- this.onDidAddNotebookEditorWidget(widget); +- } ++ // Only NotebookEditorWidget registers itself with the notebook editor service, so a type-only cast is ++ // enough here and avoids a runtime dependency on the whole notebook editor implementation ++ this.onDidAddNotebookEditorWidget(widget); + })); + + this._id = SEARCH_RESULT_PREFIX + Date.now().toString(); +diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts +index cd44270e..9f200dd3 100644 +--- a/src/vs/workbench/contrib/search/browser/searchView.ts ++++ b/src/vs/workbench/contrib/search/browser/searchView.ts +@@ -55,7 +55,8 @@ import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPan + import { IEditorPane } from '../../../common/editor.js'; + import { Memento } from '../../../common/memento.js'; + import { IViewDescriptorService } from '../../../common/views.js'; +-import { NotebookEditor } from '../../notebook/browser/notebookEditor.js'; ++import type { NotebookEditor } from '../../notebook/browser/notebookEditor.js'; ++import { NOTEBOOK_EDITOR_ID } from '../../notebook/common/notebookCommon.js'; + import { ExcludePatternInputWidget, IncludePatternInputWidget } from './patternInputWidget.js'; + import { IFindInFilesArgs } from './searchActionsBase.js'; + import { searchDetailsIcon } from './searchIcons.js'; +@@ -2269,13 +2270,15 @@ export class SearchView extends ViewPane { + return; + } + +- if (editor instanceof NotebookEditor) { ++ // Checking the editor id rather than using `instanceof` avoids a runtime dependency on the notebook editor ++ if (editor?.getId() === NOTEBOOK_EDITOR_ID) { ++ const notebookEditor = editor as NotebookEditor; + const elemParent = element.parent() as INotebookFileInstanceMatch; + if (isSearchTreeMatch(element)) { + if (isIMatchInNotebook(element)) { + element.parent().showMatch(element); + } else { +- const editorWidget = editor.getControl(); ++ const editorWidget = notebookEditor.getControl(); + if (editorWidget) { + // Ensure that the editor widget is binded. If if is, then this should return immediately. + // Otherwise, it will bind the widget. +diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +index 129e228a..6c4ba470 100644 +--- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts ++++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +@@ -85,7 +85,7 @@ import { IPathService } from '../../../services/path/common/pathService.js'; + import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; + import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; + import { IViewsService } from '../../../services/views/common/viewsService.js'; +-import { CHAT_OPEN_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; ++import { CHAT_OPEN_ACTION_ID } from '../../chat/browser/actions/chatConstants.js'; + import { IChatAgentService } from '../../chat/common/participants/chatAgents.js'; + import { IChatService } from '../../chat/common/chatService/chatService.js'; + import { configureTaskIcon, isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, QUICKOPEN_SKIP_CONFIG, TaskQuickPick } from './taskQuickPick.js'; +diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +index 0294c9fa..a7edf1ca 100644 +--- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts ++++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +@@ -29,7 +29,7 @@ import { MarkdownString } from '../../../../../base/common/htmlContent.js'; + import { IChatContextPickService } from '../../../chat/browser/attachments/chatContextPickService.js'; + import { IChatWidgetService } from '../../../chat/browser/chat.js'; + import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +-import { TerminalContext } from '../../../chat/browser/actions/chatContext.js'; ++import type { TerminalContext } from '../../../chat/browser/actions/chatContext.js'; + import { getTerminalUri, parseTerminalUri } from '../terminalUri.js'; + import { URI } from '../../../../../base/common/uri.js'; + import { ChatAgentLocation } from '../../../chat/common/constants.js'; +@@ -547,6 +547,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon, IDeco + let terminalContext: TerminalContext | undefined; + if (this._resource) { + const parsedUri = parseTerminalUri(this._resource); ++ // Loaded lazily: the chat context implementation is only needed once a command is attached to chat ++ const { TerminalContext } = await import('../../../chat/browser/actions/chatContext.js'); + terminalContext = this._instantiationService.createInstance(TerminalContext, getTerminalUri(parsedUri.workspaceId, parsedUri.instanceId!, undefined, command.id)); + } + +diff --git a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts +index e31e406c..3ef69f71 100644 +--- a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts ++++ b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts +@@ -42,7 +42,7 @@ import { ACTION_ID_NEW_CHAT } from '../../chat/browser/actions/chatActions.js'; + import { IAgentSessionsService } from '../../chat/browser/agentSessions/agentSessionsService.js'; + import { AgentSessionProviders, AgentSessionTarget } from '../../chat/browser/agentSessions/agentSessions.js'; + import { IAgentSession } from '../../chat/browser/agentSessions/agentSessionsModel.js'; +-import { AgentSessionsWelcomeEditorOptions, AgentSessionsWelcomeInput, AgentSessionsWelcomeWorkspaceKind } from './agentSessionsWelcomeInput.js'; ++import { AgentSessionsWelcomeEditorOptions, AgentSessionsWelcomeInput, AgentSessionsWelcomeWorkspaceKind, agentSessionsWelcomeCommandId } from './agentSessionsWelcomeInput.js'; + import { IChatService } from '../../chat/common/chatService/chatService.js'; + import { IChatModel } from '../../chat/common/model/chatModel.js'; + import { ChatViewId, IChatWidgetService, ISessionTypePickerDelegate, IWorkspacePickerDelegate, IWorkspacePickerItem } from '../../chat/browser/chat.js'; +@@ -129,7 +129,7 @@ type AgentSessionsWelcomeActionEvent = { + export class AgentSessionsWelcomePage extends EditorPane { + + static readonly ID = 'agentSessionsWelcomePage'; +- static readonly COMMAND_ID = 'workbench.action.openAgentSessionsWelcome'; ++ static readonly COMMAND_ID = agentSessionsWelcomeCommandId; + + private container!: HTMLElement; + private contentContainer!: HTMLElement; +diff --git a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcomeInput.ts b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcomeInput.ts +index 4057dedf..de88c08b 100644 +--- a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcomeInput.ts ++++ b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcomeInput.ts +@@ -11,6 +11,7 @@ import { IUntypedEditorInput } from '../../../common/editor.js'; + import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; + + export const agentSessionsWelcomeInputTypeId = 'workbench.editors.agentSessionsWelcomeInput'; ++export const agentSessionsWelcomeCommandId = 'workbench.action.openAgentSessionsWelcome'; + export type AgentSessionsWelcomeInitiator = 'startup' | 'command'; + export type AgentSessionsWelcomeWorkspaceKind = 'empty' | 'folder' | 'workspace'; + +diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +index f90937e6..0b9fb3a1 100644 +--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts ++++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +@@ -32,7 +32,7 @@ import { Categories } from '../../../../platform/action/common/actionCommonCateg + import { DisposableStore } from '../../../../base/common/lifecycle.js'; + import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; + import { GettingStartedAccessibleView } from './gettingStartedAccessibleView.js'; +-import { AgentSessionsWelcomePage } from '../../welcomeAgentSessions/browser/agentSessionsWelcome.js'; ++import { agentSessionsWelcomeCommandId } from '../../welcomeAgentSessions/browser/agentSessionsWelcomeInput.js'; + import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; + import './gettingStartedService._contribution.js'; + +@@ -72,7 +72,7 @@ registerAction2(class extends Action2 { + + // If no specific walkthrough is requested and agent sessions welcome is preferred, open that instead + if (!walkthroughID && !chatEntitlementService.sentiment.hidden && configurationService.getValue('workbench.startupEditor') === 'agentSessionsWelcomePage') { +- commandService.executeCommand(AgentSessionsWelcomePage.COMMAND_ID); ++ commandService.executeCommand(agentSessionsWelcomeCommandId); + return; + } else { + if (walkthroughID) { +-- +2.39.5 (Apple Git-154) + diff --git a/vscode-patches/0110-refactor-split-extension-host-contribution-per-featu.patch b/vscode-patches/0110-refactor-split-extension-host-contribution-per-featu.patch new file mode 100644 index 00000000..014efee7 --- /dev/null +++ b/vscode-patches/0110-refactor-split-extension-host-contribution-per-featu.patch @@ -0,0 +1,286 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brian Jenkins +Date: Mon, 28 Sep 2026 00:30:27 -0400 +Subject: [PATCH] refactor: split extension host contribution per feature + +extensionHost.contribution still imports everything, but the chat, notebook +and mcp mainThread participants now live in their own files next to an +extensionHost.base.contribution, so they can be loaded only when needed. + +Co-Authored-By: Claude Opus 5.5 +--- + .../extensionHost.base.contribution.ts | 90 +++++++++++++++ + .../extensionHost.chat.contribution.ts | 16 +++ + .../api/browser/extensionHost.contribution.ts | 105 +----------------- + .../browser/extensionHost.mcp.contribution.ts | 7 ++ + .../extensionHost.notebook.contribution.ts | 11 ++ + 5 files changed, 128 insertions(+), 101 deletions(-) + create mode 100644 src/vs/workbench/api/browser/extensionHost.base.contribution.ts + create mode 100644 src/vs/workbench/api/browser/extensionHost.chat.contribution.ts + create mode 100644 src/vs/workbench/api/browser/extensionHost.mcp.contribution.ts + create mode 100644 src/vs/workbench/api/browser/extensionHost.notebook.contribution.ts + +diff --git a/src/vs/workbench/api/browser/extensionHost.base.contribution.ts b/src/vs/workbench/api/browser/extensionHost.base.contribution.ts +new file mode 100644 +index 00000000..bb2c5531 +--- /dev/null ++++ b/src/vs/workbench/api/browser/extensionHost.base.contribution.ts +@@ -0,0 +1,90 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright (c) Microsoft Corporation. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++ ++// --- other interested parties ++import '../common/jsonValidationExtensionPoint.js'; ++import '../../services/themes/common/colorExtensionPoint.js'; ++import '../../services/themes/common/iconExtensionPoint.js'; ++import '../../services/themes/common/tokenClassificationExtensionPoint.js'; ++import '../../contrib/codeEditor/common/languageConfigurationExtensionPoint.js'; ++import './statusBarExtensionPoint.js'; ++import '../../services/themes/browser/cssExtensionPoint.js'; ++ ++// --- mainThread participants ++import './mainThreadAgentEditorComments.js'; ++import './mainThreadLocalization.js'; ++import './mainThreadBulkEdits.js'; ++import './mainThreadEmbeddings.js'; ++import './mainThreadCodeInsets.js'; ++import './mainThreadCLICommands.js'; ++import './mainThreadClipboard.js'; ++import './mainThreadCommands.js'; ++import './mainThreadConfiguration.js'; ++import './mainThreadConsole.js'; ++import './mainThreadDebugService.js'; ++import './mainThreadDecorations.js'; ++import './mainThreadDiagnostics.js'; ++import './mainThreadDialogs.js'; ++import './mainThreadDocumentContentProviders.js'; ++import './mainThreadDocuments.js'; ++import './mainThreadDocumentsAndEditors.js'; ++import './mainThreadEditor.js'; ++import './mainThreadEditors.js'; ++import './mainThreadEditorTabs.js'; ++import './mainThreadErrors.js'; ++import './mainThreadExtensionService.js'; ++import './mainThreadFileSystem.js'; ++import './mainThreadFileSystemEventService.js'; ++import './mainThreadLanguageFeatures.js'; ++import './mainThreadLanguages.js'; ++import './mainThreadLogService.js'; ++import './mainThreadMessageService.js'; ++import './mainThreadManagedSockets.js'; ++import './mainThreadBrowserTunnelProxy.js'; ++import './mainThreadOutputService.js'; ++import './mainThreadProgress.js'; ++import './mainThreadQuickDiff.js'; ++import './mainThreadDocumentDiff.js'; ++import './mainThreadQuickOpen.js'; ++import './mainThreadRemoteConnectionData.js'; ++import './mainThreadSaveParticipant.js'; ++import './mainThreadSpeech.js'; ++import './mainThreadEditSessionIdentityParticipant.js'; ++import './mainThreadSCM.js'; ++import './mainThreadSearch.js'; ++import './mainThreadStatusBar.js'; ++import './mainThreadStorage.js'; ++import './mainThreadTelemetry.js'; ++import './mainThreadTerminalService.js'; ++import './mainThreadTerminalShellIntegration.js'; ++import './mainThreadTheming.js'; ++import './mainThreadTreeViews.js'; ++import './mainThreadDownloadService.js'; ++import './mainThreadUrls.js'; ++import './mainThreadUriOpeners.js'; ++import './mainThreadWindow.js'; ++import './mainThreadPower.js'; ++import './mainThreadWebviewManager.js'; ++import './mainThreadWorkspace.js'; ++import './mainThreadComments.js'; ++import './mainThreadInteractive.js'; ++import './mainThreadTask.js'; ++import './mainThreadLabelService.js'; ++import './mainThreadTunnelService.js'; ++import './mainThreadAuthentication.js'; ++import './mainThreadTimeline.js'; ++import './mainThreadTesting.js'; ++import './mainThreadSecretState.js'; ++import './mainThreadShare.js'; ++import './mainThreadProfileContentHandlers.js'; ++import './mainThreadAiRelatedInformation.js'; ++import './mainThreadAiEmbeddingVector.js'; ++import './mainThreadAiSettingsSearch.js'; ++import './mainThreadChatOutputRenderer.js'; ++import './mainThreadDataChannels.js'; ++import './mainThreadMeteredConnection.js'; ++import './mainThreadGitExtensionService.js'; ++import './mainThreadBrowsers.js'; +diff --git a/src/vs/workbench/api/browser/extensionHost.chat.contribution.ts b/src/vs/workbench/api/browser/extensionHost.chat.contribution.ts +new file mode 100644 +index 00000000..c319d415 +--- /dev/null ++++ b/src/vs/workbench/api/browser/extensionHost.chat.contribution.ts +@@ -0,0 +1,16 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright (c) Microsoft Corporation. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++// Chat and language model mainThread participants, split out of extensionHost.contribution so they can be loaded separately ++import './mainThreadLanguageModels.js'; ++import './mainThreadChatAgents2.js'; ++import './mainThreadChatCodeMapper.js'; ++import './mainThreadLanguageModelTools.js'; ++import './mainThreadChatContext.js'; ++import './mainThreadChatDebug.js'; ++import './mainThreadChatStatus.js'; ++import './mainThreadChatQuota.js'; ++import './mainThreadChatInputNotification.js'; ++import './mainThreadChatSessions.js'; +diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts +index 82aee03d..93e9b81f 100644 +--- a/src/vs/workbench/api/browser/extensionHost.contribution.ts ++++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts +@@ -3,104 +3,7 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +- +-// --- other interested parties +-import '../common/jsonValidationExtensionPoint.js'; +-import '../../services/themes/common/colorExtensionPoint.js'; +-import '../../services/themes/common/iconExtensionPoint.js'; +-import '../../services/themes/common/tokenClassificationExtensionPoint.js'; +-import '../../contrib/codeEditor/common/languageConfigurationExtensionPoint.js'; +-import './statusBarExtensionPoint.js'; +-import '../../services/themes/browser/cssExtensionPoint.js'; +- +-// --- mainThread participants +-import './mainThreadAgentEditorComments.js'; +-import './mainThreadLocalization.js'; +-import './mainThreadBulkEdits.js'; +-import './mainThreadLanguageModels.js'; +-import './mainThreadChatAgents2.js'; +-import './mainThreadChatCodeMapper.js'; +-import './mainThreadLanguageModelTools.js'; +-import './mainThreadEmbeddings.js'; +-import './mainThreadCodeInsets.js'; +-import './mainThreadCLICommands.js'; +-import './mainThreadClipboard.js'; +-import './mainThreadCommands.js'; +-import './mainThreadConfiguration.js'; +-import './mainThreadConsole.js'; +-import './mainThreadDebugService.js'; +-import './mainThreadDecorations.js'; +-import './mainThreadDiagnostics.js'; +-import './mainThreadDialogs.js'; +-import './mainThreadDocumentContentProviders.js'; +-import './mainThreadDocuments.js'; +-import './mainThreadDocumentsAndEditors.js'; +-import './mainThreadEditor.js'; +-import './mainThreadEditors.js'; +-import './mainThreadEditorTabs.js'; +-import './mainThreadErrors.js'; +-import './mainThreadExtensionService.js'; +-import './mainThreadFileSystem.js'; +-import './mainThreadFileSystemEventService.js'; +-import './mainThreadLanguageFeatures.js'; +-import './mainThreadLanguages.js'; +-import './mainThreadLogService.js'; +-import './mainThreadMessageService.js'; +-import './mainThreadManagedSockets.js'; +-import './mainThreadBrowserTunnelProxy.js'; +-import './mainThreadOutputService.js'; +-import './mainThreadProgress.js'; +-import './mainThreadQuickDiff.js'; +-import './mainThreadDocumentDiff.js'; +-import './mainThreadQuickOpen.js'; +-import './mainThreadRemoteConnectionData.js'; +-import './mainThreadSaveParticipant.js'; +-import './mainThreadSpeech.js'; +-import './mainThreadEditSessionIdentityParticipant.js'; +-import './mainThreadSCM.js'; +-import './mainThreadSearch.js'; +-import './mainThreadStatusBar.js'; +-import './mainThreadStorage.js'; +-import './mainThreadTelemetry.js'; +-import './mainThreadTerminalService.js'; +-import './mainThreadTerminalShellIntegration.js'; +-import './mainThreadTheming.js'; +-import './mainThreadTreeViews.js'; +-import './mainThreadDownloadService.js'; +-import './mainThreadUrls.js'; +-import './mainThreadUriOpeners.js'; +-import './mainThreadWindow.js'; +-import './mainThreadPower.js'; +-import './mainThreadWebviewManager.js'; +-import './mainThreadWorkspace.js'; +-import './mainThreadComments.js'; +-import './mainThreadNotebook.js'; +-import './mainThreadNotebookKernels.js'; +-import './mainThreadNotebookDocumentsAndEditors.js'; +-import './mainThreadNotebookRenderers.js'; +-import './mainThreadNotebookSaveParticipant.js'; +-import './mainThreadInteractive.js'; +-import './mainThreadTask.js'; +-import './mainThreadLabelService.js'; +-import './mainThreadTunnelService.js'; +-import './mainThreadAuthentication.js'; +-import './mainThreadTimeline.js'; +-import './mainThreadTesting.js'; +-import './mainThreadSecretState.js'; +-import './mainThreadShare.js'; +-import './mainThreadProfileContentHandlers.js'; +-import './mainThreadAiRelatedInformation.js'; +-import './mainThreadAiEmbeddingVector.js'; +-import './mainThreadAiSettingsSearch.js'; +-import './mainThreadMcp.js'; +-import './mainThreadChatContext.js'; +-import './mainThreadChatDebug.js'; +-import './mainThreadChatStatus.js'; +-import './mainThreadChatQuota.js'; +-import './mainThreadChatInputNotification.js'; +-import './mainThreadChatOutputRenderer.js'; +-import './mainThreadChatSessions.js'; +-import './mainThreadDataChannels.js'; +-import './mainThreadMeteredConnection.js'; +-import './mainThreadGitExtensionService.js'; +-import './mainThreadBrowsers.js'; ++import './extensionHost.base.contribution.js'; ++import './extensionHost.chat.contribution.js'; ++import './extensionHost.notebook.contribution.js'; ++import './extensionHost.mcp.contribution.js'; +diff --git a/src/vs/workbench/api/browser/extensionHost.mcp.contribution.ts b/src/vs/workbench/api/browser/extensionHost.mcp.contribution.ts +new file mode 100644 +index 00000000..570c524a +--- /dev/null ++++ b/src/vs/workbench/api/browser/extensionHost.mcp.contribution.ts +@@ -0,0 +1,7 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright (c) Microsoft Corporation. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++// MCP mainThread participants, split out of extensionHost.contribution so they can be loaded separately ++import './mainThreadMcp.js'; +diff --git a/src/vs/workbench/api/browser/extensionHost.notebook.contribution.ts b/src/vs/workbench/api/browser/extensionHost.notebook.contribution.ts +new file mode 100644 +index 00000000..ff2f3cc2 +--- /dev/null ++++ b/src/vs/workbench/api/browser/extensionHost.notebook.contribution.ts +@@ -0,0 +1,11 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright (c) Microsoft Corporation. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++// Notebook mainThread participants, split out of extensionHost.contribution so they can be loaded separately ++import './mainThreadNotebook.js'; ++import './mainThreadNotebookKernels.js'; ++import './mainThreadNotebookDocumentsAndEditors.js'; ++import './mainThreadNotebookRenderers.js'; ++import './mainThreadNotebookSaveParticipant.js'; +-- +2.39.5 (Apple Git-154) +