From ec8586ef251f560f4deaaa205d67bc32213b5055 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 16 Oct 2025 21:11:11 -0700 Subject: [PATCH] Add isModelProxyAvailable (#271872) * Add isModelProxyAvailable * tests --- extensions/vscode-api-tests/package.json | 3 ++- .../workbench/api/common/extHost.api.impl.ts | 16 +++++++++--- .../api/common/extHostLanguageModels.ts | 26 +++++++++++++------ .../vscode.proposed.languageModelProxy.d.ts | 17 +++++++++--- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 74c7eacd7e1..aaad54485df 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -51,7 +51,8 @@ "tunnels", "workspaceTrust", "inlineCompletionsAdditions", - "devDeviceId" + "devDeviceId", + "languageModelProxy" ], "private": true, "activationEvents": [], diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 463fe9c7201..a542aed9209 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -27,14 +27,17 @@ import { ExtensionDescriptionRegistry } from '../../services/extensions/common/e import { UIKind } from '../../services/extensions/common/extensionHostProtocol.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ProxyIdentifier } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContext2, TextSearchMatch2, AISearchKeyword } from '../../services/search/common/searchExtTypes.js'; +import { AISearchKeyword, ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContext2, TextSearchMatch2 } from '../../services/search/common/searchExtTypes.js'; import { CandidatePortSource, ExtHostContext, ExtHostLogLevelServiceShape, MainContext } from './extHost.protocol.js'; import { ExtHostRelatedInformation } from './extHostAiRelatedInformation.js'; +import { ExtHostAiSettingsSearch } from './extHostAiSettingsSearch.js'; import { ExtHostApiCommands } from './extHostApiCommands.js'; import { IExtHostApiDeprecationService } from './extHostApiDeprecationService.js'; import { IExtHostAuthentication } from './extHostAuthentication.js'; import { ExtHostBulkEdits } from './extHostBulkEdits.js'; import { ExtHostChatAgents2 } from './extHostChatAgents2.js'; +import { ExtHostChatOutputRenderer } from './extHostChatOutputRenderer.js'; +import { ExtHostChatSessions } from './extHostChatSessions.js'; import { ExtHostChatStatus } from './extHostChatStatus.js'; import { ExtHostClipboard } from './extHostClipboard.js'; import { ExtHostEditorInsets } from './extHostCodeInsets.js'; @@ -111,9 +114,6 @@ import { ExtHostWebviewPanels } from './extHostWebviewPanels.js'; import { ExtHostWebviewViews } from './extHostWebviewView.js'; import { IExtHostWindow } from './extHostWindow.js'; import { IExtHostWorkspace } from './extHostWorkspace.js'; -import { ExtHostAiSettingsSearch } from './extHostAiSettingsSearch.js'; -import { ExtHostChatSessions } from './extHostChatSessions.js'; -import { ExtHostChatOutputRenderer } from './extHostChatOutputRenderer.js'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -1552,6 +1552,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerLanguageModelChatProvider: (vendor, provider) => { return extHostLanguageModels.registerLanguageModelChatProvider(extension, vendor, provider); }, + get isModelProxyAvailable() { + checkProposedApiEnabled(extension, 'languageModelProxy'); + return extHostLanguageModels.isModelProxyAvailable; + }, + onDidChangeModelProxyAvailability: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'languageModelProxy'); + return extHostLanguageModels.onDidChangeModelProxyAvailability(listener, thisArgs, disposables); + }, getModelProxy: () => { checkProposedApiEnabled(extension, 'languageModelProxy'); return extHostLanguageModels.getModelProxy(extension); diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 5011da45902..05a8e6c10e7 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -114,6 +114,8 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; + private readonly _onDidChangeModelProxyAvailability = new Emitter(); + readonly onDidChangeModelProxyAvailability = this._onDidChangeModelProxyAvailability.event; private readonly _languageModelProviders = new Map(); // TODO @lramos15 - Remove the need for both info and metadata as it's a lot of redundancy. Should just need one @@ -134,6 +136,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { dispose(): void { this._onDidChangeModelAccess.dispose(); this._onDidChangeProviders.dispose(); + this._onDidChangeModelProxyAvailability.dispose(); } registerLanguageModelChatProvider(extension: IExtensionDescription, vendor: string, provider: vscode.LanguageModelChatProvider): IDisposable { @@ -606,25 +609,30 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { return this._proxy.$fileIsIgnored(uri, token); } - async getModelProxy(extension: IExtensionDescription): Promise { + get isModelProxyAvailable(): boolean { + return !!this._languageModelProxyProvider; + } + + async getModelProxy(extension: IExtensionDescription): Promise { checkProposedApiEnabled(extension, 'languageModelProxy'); if (!this._languageModelProxyProvider) { - this._logService.warn('[LanguageModelProxy] No LanguageModelProxyProvider registered'); - return undefined; + this._logService.trace('[LanguageModelProxy] No LanguageModelProxyProvider registered'); + throw new Error('No language model proxy provider is registered.'); } const requestingExtensionId = ExtensionIdentifier.toKey(extension.identifier); try { const result = await Promise.resolve(this._languageModelProxyProvider.provideModelProxy(requestingExtensionId, CancellationToken.None)); - if (result) { - return result; + if (!result) { + this._logService.warn(`[LanguageModelProxy] Provider returned no proxy for ${requestingExtensionId}`); + throw new Error('Language model proxy is not available.'); } + return result; } catch (err) { - this._logService.error(`[LanguageModelProxy] Provider ${ExtensionIdentifier.toKey(extension.identifier)} failed`, err); + this._logService.error(`[LanguageModelProxy] Provider failed to return proxy for ${requestingExtensionId}`, err); + throw err; } - - return undefined; } async $isFileIgnored(handle: number, uri: UriComponents, token: CancellationToken): Promise { @@ -652,9 +660,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { checkProposedApiEnabled(extension, 'chatParticipantPrivate'); this._languageModelProxyProvider = provider; + this._onDidChangeModelProxyAvailability.fire(); return toDisposable(() => { if (this._languageModelProxyProvider === provider) { this._languageModelProxyProvider = undefined; + this._onDidChangeModelProxyAvailability.fire(); } }); } diff --git a/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts index 849e08d4529..6d32e6de3f8 100644 --- a/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts @@ -11,10 +11,21 @@ declare module 'vscode' { export namespace lm { /** - * Returns undefined if + * Returns false if + * - Copilot Chat extension is not installed + * - Copilot Chat has not finished activating or finished auth * - The user is not logged in, or isn't the right SKU, with expected model access - * - The server fails to start for some reason */ - export function getModelProxy(): Thenable; + export const isModelProxyAvailable: boolean; + + /** + * Fired when isModelProxyAvailable changes. + */ + export const onDidChangeModelProxyAvailability: Event; + + /** + * Throws if the server fails to start for some reason, or something else goes wrong. + */ + export function getModelProxy(): Thenable; } }