chat - towards experimental anonymous access (#265806)

This commit is contained in:
Benjamin Pasero
2025-09-09 16:15:50 +02:00
committed by GitHub
parent 261786351d
commit 8a48ecc72b
8 changed files with 94 additions and 22 deletions

View File

@@ -50,7 +50,8 @@
"treeViewReveal",
"tunnels",
"workspaceTrust",
"inlineCompletionsAdditions"
"inlineCompletionsAdditions",
"devDeviceId"
],
"private": true,
"activationEvents": [],

View File

@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.105.0",
"distro": "34ee7cb7a51777c9233b9b97a87ed494d4bdeb0a",
"distro": "1c56627fcbb7427c61b3debf10778ac069ba3027",
"author": {
"name": "Microsoft Corporation"
},

View File

@@ -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',
},

View File

@@ -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; },

View File

@@ -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<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(

View File

@@ -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<IChatSetupResult> {
async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise<IChatSetupResult> {
if (this.pendingRun) {
return this.pendingRun;
}
@@ -663,7 +667,7 @@ class ChatSetup {
}
}
private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[] }): Promise<IChatSetupResult> {
private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise<IChatSetupResult> {
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<void> {
// 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 {

View File

@@ -43,6 +43,7 @@ export namespace ChatEntitlementContextKeys {
export const Entitlement = {
signedOut: new RawContextKey<boolean>('chatEntitlementSignedOut', false, true), // True when user is signed out.
anonymous: new RawContextKey<boolean>('chatEntitlementAnonymous', false, true), // True when user is an anonymous user.
canSignUp: new RawContextKey<boolean>('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat free user.
planFree: new RawContextKey<boolean>('chatPlanFree', false, true), // True when user is a chat free user.
@@ -63,24 +64,26 @@ export namespace ChatEntitlementContextKeys {
export const IChatEntitlementService = createDecorator<IChatEntitlementService>('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<boolean>(ChatEntitlementContextKeys.Entitlement.signedOut.key) === true) {
return ChatEntitlement.Unknown;
} else if (this.contextKeyService.getContextKeyValue<boolean>(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<boolean>;
private readonly signedOutContextKey: IContextKey<boolean>;
private readonly anonymousContextKey: IContextKey<boolean>;
private readonly freeContextKey: IContextKey<boolean>;
private readonly proContextKey: IContextKey<boolean>;
@@ -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);

View File

@@ -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;
}
}