diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 43583bb8e58..250ec7ae327 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -50,7 +50,8 @@ "treeViewReveal", "tunnels", "workspaceTrust", - "inlineCompletionsAdditions" + "inlineCompletionsAdditions", + "devDeviceId" ], "private": true, "activationEvents": [], diff --git a/package.json b/package.json index b368ed81dc7..2e2633f90d5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "34ee7cb7a51777c9233b9b97a87ed494d4bdeb0a", + "distro": "1c56627fcbb7427c61b3debf10778ac069ba3027", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index cf9a8fd7dc5..a2e111a0a46 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -184,6 +184,9 @@ const _allApiProposals = { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts', version: 4 }, + devDeviceId: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.devDeviceId.d.ts', + }, diffCommand: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b0e0d18be0d..0bb808d594a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -384,6 +384,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: env const env: typeof vscode.env = { get machineId() { return initData.telemetryInfo.machineId; }, + get devDeviceId() { + checkProposedApiEnabled(extension, 'devDeviceId'); + return initData.telemetryInfo.devDeviceId; + }, get sessionId() { return initData.telemetryInfo.sessionId; }, get language() { return initData.environment.appLanguage; }, get appName() { return initData.environment.appName; }, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 3af4c960e73..39bb8fd7ed9 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -660,7 +660,15 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize('chat.showAgentSessionsViewDescription', "Controls whether session descriptions are displayed on a second row in the Chat Sessions view."), default: true, - } + }, + 'chat.allowAnonymousAccess': { // TODO@bpasero remove me eventually + type: 'boolean', + default: false, + tags: ['experimental'], + experiment: { + mode: 'auto' + } + }, } }); Registry.as(EditorExtensions.EditorPane).registerEditorPane( diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 417c05b25eb..7815c69b227 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './media/chatSetup.css'; import { $ } from '../../../../base/browser/dom.js'; import { IButton } from '../../../../base/browser/ui/button/button.js'; import { Dialog, DialogContentsAlignment } from '../../../../base/browser/ui/dialog/dialog.js'; @@ -52,7 +53,7 @@ import { AuthenticationSession, IAuthenticationService } from '../../../services import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../services/extensionManagement/common/extensionManagement.js'; import { ExtensionUrlHandlerOverrideRegistry } from '../../../services/extensions/browser/extensionUrlHandler.js'; -import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js'; +import { IExtensionService, nullExtensionDescription } from '../../../services/extensions/common/extensions.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; @@ -73,7 +74,7 @@ import { ILanguageModelsService } from '../common/languageModels.js'; import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from './actions/chatActions.js'; import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; -import './media/chatSetup.css'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; import { chatViewsWelcomeRegistry } from './viewsWelcome/chatViewsWelcome.js'; const defaultChat = { @@ -439,7 +440,10 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let result: IChatSetupResult | undefined = undefined; try { - result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ disableChatViewReveal: true /* we are already in a chat context */ }); + result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ + disableChatViewReveal: true, // we are already in a chat context + allowAnonymous: true, // in chat context we can allow anonymous usage (TODO@bpasero make this dependent on terms visibility) + }); } catch (error) { this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`); } finally { @@ -649,7 +653,7 @@ class ChatSetup { this.skipDialogOnce = true; } - async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[] }): Promise { + async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise { if (this.pendingRun) { return this.pendingRun; } @@ -663,7 +667,7 @@ class ChatSetup { } } - private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[] }): Promise { + private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise { this.context.update({ later: false }); const dialogSkipped = this.skipDialogOnce; @@ -682,6 +686,8 @@ class ChatSetup { let setupStrategy: ChatSetupStrategy; if (!options?.forceSignInDialog && (dialogSkipped || isProUser(this.chatEntitlementService.entitlement) || this.chatEntitlementService.entitlement === ChatEntitlement.Free)) { setupStrategy = ChatSetupStrategy.DefaultSetup; // existing pro/free users setup without a dialog + } else if (options?.allowAnonymous && this.chatEntitlementService.entitlement === ChatEntitlement.Anonymous) { + setupStrategy = ChatSetupStrategy.DefaultSetup; } else { setupStrategy = await this.showDialog(options); } @@ -829,6 +835,8 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService private readonly extensionService: IExtensionService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); @@ -1152,6 +1160,15 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr private async checkExtensionInstallation(context: ChatEntitlementContext): Promise { + // When developing extensions, await registration and then check + if (this.environmentService.isExtensionDevelopment) { + await this.extensionService.whenInstalledExtensionsRegistered(); + if (this.extensionService.extensions.find(ext => ExtensionIdentifier.equals(ext.identifier, defaultChat.chatExtensionId))) { + context.update({ installed: true, disabled: false, untrusted: false }); + return; + } + } + // Await extensions to be ready to be queried await this.extensionsWorkbenchService.queryLocal(); @@ -1461,9 +1478,10 @@ class ChatSetupController extends Disposable { let sessions = session ? [session] : undefined; try { if ( - entitlement !== ChatEntitlement.Free && // User is not signed up to Copilot Free - !isProUser(entitlement) && // User is not signed up for a Copilot subscription - entitlement !== ChatEntitlement.Unavailable // User is eligible for Copilot Free + entitlement !== ChatEntitlement.Anonymous && // User is not eligible for anonymous access + entitlement !== ChatEntitlement.Free && // User is not signed up to Copilot Free + !isProUser(entitlement) && // User is not signed up for a Copilot subscription + entitlement !== ChatEntitlement.Unavailable // User is eligible for Copilot Free ) { if (!sessions) { try { diff --git a/src/vs/workbench/services/chat/common/chatEntitlementService.ts b/src/vs/workbench/services/chat/common/chatEntitlementService.ts index d01ea08ba42..771ac6b1420 100644 --- a/src/vs/workbench/services/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/services/chat/common/chatEntitlementService.ts @@ -43,6 +43,7 @@ export namespace ChatEntitlementContextKeys { export const Entitlement = { signedOut: new RawContextKey('chatEntitlementSignedOut', false, true), // True when user is signed out. + anonymous: new RawContextKey('chatEntitlementAnonymous', false, true), // True when user is an anonymous user. canSignUp: new RawContextKey('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat free user. planFree: new RawContextKey('chatPlanFree', false, true), // True when user is a chat free user. @@ -63,24 +64,26 @@ export namespace ChatEntitlementContextKeys { export const IChatEntitlementService = createDecorator('chatEntitlementService'); export enum ChatEntitlement { + /* Signed out, anonymous */ + Anonymous = -1, /** Signed out */ Unknown = 1, /** Signed in but not yet resolved */ - Unresolved, + Unresolved = 2, /** Signed in and entitled to Free */ - Available, + Available = 3, /** Signed in but not entitled to Free */ - Unavailable, + Unavailable = 4, /** Signed-up to Free */ - Free, + Free = 5, /** Signed-up to Pro */ - Pro, + Pro = 6, /** Signed-up to Pro Plus */ - ProPlus, + ProPlus = 7, /** Signed-up to Business */ - Business, + Business = 8, /** Signed-up to Enterprise */ - Enterprise + Enterprise = 9, } export interface IChatSentiment { @@ -209,6 +212,7 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme ChatEntitlementContextKeys.Entitlement.planFree.key, ChatEntitlementContextKeys.Entitlement.canSignUp.key, ChatEntitlementContextKeys.Entitlement.signedOut.key, + ChatEntitlementContextKeys.Entitlement.anonymous.key, ChatEntitlementContextKeys.Entitlement.organisations.key, ChatEntitlementContextKeys.Entitlement.internal.key, ChatEntitlementContextKeys.Entitlement.sku.key @@ -269,6 +273,8 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme return ChatEntitlement.Available; } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.signedOut.key) === true) { return ChatEntitlement.Unknown; + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.anonymous.key) === true) { + return ChatEntitlement.Anonymous; } return ChatEntitlement.Unresolved; @@ -1008,10 +1014,13 @@ type ChatEntitlementEvent = { export class ChatEntitlementContext extends Disposable { private static readonly CHAT_ENTITLEMENT_CONTEXT_STORAGE_KEY = 'chat.setupContext'; + private static readonly CHAT_DISABLED_CONFIGURATION_KEY = 'chat.disableAIFeatures'; + private static readonly CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY = 'chat.allowAnonymousAccess'; private readonly canSignUpContextKey: IContextKey; private readonly signedOutContextKey: IContextKey; + private readonly anonymousContextKey: IContextKey; private readonly freeContextKey: IContextKey; private readonly proContextKey: IContextKey; @@ -1049,6 +1058,8 @@ export class ChatEntitlementContext extends Disposable { this.canSignUpContextKey = ChatEntitlementContextKeys.Entitlement.canSignUp.bindTo(contextKeyService); this.signedOutContextKey = ChatEntitlementContextKeys.Entitlement.signedOut.bindTo(contextKeyService); + this.anonymousContextKey = ChatEntitlementContextKeys.Entitlement.anonymous.bindTo(contextKeyService); + this.freeContextKey = ChatEntitlementContextKeys.Entitlement.planFree.bindTo(contextKeyService); this.proContextKey = ChatEntitlementContextKeys.Entitlement.planPro.bindTo(contextKeyService); this.proPlusContextKey = ChatEntitlementContextKeys.Entitlement.planProPlus.bindTo(contextKeyService); @@ -1072,7 +1083,7 @@ export class ChatEntitlementContext extends Disposable { private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY)) { + if (e.affectsConfiguration(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY) || e.affectsConfiguration(ChatEntitlementContext.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY)) { this.updateContext(); } })); @@ -1080,8 +1091,19 @@ export class ChatEntitlementContext extends Disposable { private withConfiguration(state: IChatEntitlementContextState): IChatEntitlementContextState { if (this.configurationService.getValue(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY) === true) { - // Setting always wins: if AI is disabled, set `hidden: true` - return { ...state, hidden: true }; + return { + ...state, + hidden: true // Setting always wins: if AI is disabled, set `hidden: true` + }; + } + + if (this.configurationService.getValue(ChatEntitlementContext.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY) === true) { + let entitlement = state.entitlement; + if (entitlement === ChatEntitlement.Unknown /*&& !state.registered TODO@bpasero revisit */) { + entitlement = ChatEntitlement.Anonymous; // enable `anonymous` based on exp config if entitlement is unknown and user never signed up + } + + return { ...state, entitlement }; } return state; @@ -1148,6 +1170,7 @@ export class ChatEntitlementContext extends Disposable { const state = this.withConfiguration(this._state); this.signedOutContextKey.set(state.entitlement === ChatEntitlement.Unknown); + this.anonymousContextKey.set(state.entitlement === ChatEntitlement.Anonymous); this.canSignUpContextKey.set(state.entitlement === ChatEntitlement.Available); this.freeContextKey.set(state.entitlement === ChatEntitlement.Free); diff --git a/src/vscode-dts/vscode.proposed.devDeviceId.d.ts b/src/vscode-dts/vscode.proposed.devDeviceId.d.ts new file mode 100644 index 00000000000..0f9a62fc7db --- /dev/null +++ b/src/vscode-dts/vscode.proposed.devDeviceId.d.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export namespace env { + + /** + * An alternative unique identifier for the computer. + */ + export const devDeviceId: string; + } +}