Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/vs/workbench/api/browser/mainThreadChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import { ExtensionIdentifier } from '../../../platform/extensions/common/extensi
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../platform/log/common/log.js';
import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js';
import { IChatWidgetService } from '../../contrib/chat/browser/chat.js';
import { IChatWidget, IChatWidgetService } from '../../contrib/chat/browser/chat.js';
import { AgentSessionProviders, getAgentSessionProvider } from '../../contrib/chat/browser/agentSessions/agentSessions.js';
import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../contrib/chat/browser/attachments/chatDynamicVariables.js';
import { IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/participants/chatAgents.js';
import { IPromptFileContext, IPromptsService } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
Expand Down Expand Up @@ -145,9 +146,18 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
this._register(this._chatService.onDidReceiveQuestionCarouselAnswer(e => {
this._proxy.$handleQuestionCarouselAnswer(e.requestId, e.resolveId, e.answers);
}));
this._register(this._chatWidgetService.onDidChangeFocusedWidget(widget => {
this._proxy.$acceptActiveChatSession(widget?.viewModel?.sessionResource);
this._register(this._chatWidgetService.onDidChangeFocusedSession(() => {
this._acceptActiveChatSession(this._chatWidgetService.lastFocusedWidget);
}));

// Push the initial active session if there is already a focused widget
this._acceptActiveChatSession(this._chatWidgetService.lastFocusedWidget);
}

private _acceptActiveChatSession(widget: IChatWidget | undefined): void {
const sessionResource = widget?.viewModel?.sessionResource;
const isLocal = sessionResource && getAgentSessionProvider(sessionResource) === AgentSessionProviders.Local;
this._proxy.$acceptActiveChatSession(isLocal ? sessionResource : undefined);
}

$unregisterAgent(handle: number): void {
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHostLanguageModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
isDefaultForLocation,
isUserSelectable: m.isUserSelectable,
statusIcon: m.statusIcon,
targetChatSessionType: m.targetChatSessionType,
modelPickerCategory: m.category ?? DEFAULT_MODEL_PICKER_CATEGORY,
capabilities: m.capabilities ? {
vision: m.capabilities.imageInput,
Expand Down
10 changes: 7 additions & 3 deletions src/vs/workbench/browser/parts/editor/modalEditorPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class ModalEditorPart {
ModalEditorPartImpl,
mainWindow.vscodeWindowId,
this.editorPartsView,
localize('modalEditorPart', "Modal Editor Area")
modalElement,
));
disposables.add(this.editorPartsView.registerPart(editorPart));
editorPart.create(editorPartContainer);
Expand Down Expand Up @@ -240,7 +240,7 @@ class ModalEditorPartImpl extends EditorPart implements IModalEditorPart {
constructor(
windowId: number,
editorPartsView: IEditorPartsView,
groupsLabel: string,
private readonly modalElement: HTMLElement,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
Expand All @@ -250,11 +250,15 @@ class ModalEditorPartImpl extends EditorPart implements IModalEditorPart {
@IContextKeyService contextKeyService: IContextKeyService,
) {
const id = ModalEditorPartImpl.COUNTER++;
super(editorPartsView, `workbench.parts.modalEditor.${id}`, groupsLabel, windowId, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService);
super(editorPartsView, `workbench.parts.modalEditor.${id}`, localize('modalEditorPart', "Modal Editor Area"), windowId, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService);

this.enforceModalPartOptions();
}

getModalElement() {
return this.modalElement;
}

override create(parent: HTMLElement, options?: object): void {
this.previousMainWindowActiveElement = mainWindow.document.activeElement;

Expand Down
19 changes: 18 additions & 1 deletion src/vs/workbench/contrib/chat/browser/actions/chatActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { ITelemetryService } from '../../../../../platform/telemetry/common/tele
import { ActiveEditorContext } from '../../../../common/contextkeys.js';
import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js';
import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';
import { ACTIVE_GROUP, AUX_WINDOW_GROUP } from '../../../../services/editor/common/editorService.js';
import { ACTIVE_GROUP, AUX_WINDOW_GROUP, SIDE_GROUP } from '../../../../services/editor/common/editorService.js';
import { IHostService } from '../../../../services/host/browser/host.js';
import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/browser/layoutService.js';
import { IPreferencesService } from '../../../../services/preferences/common/preferences.js';
Expand Down Expand Up @@ -695,6 +695,23 @@ export function registerChatActions() {
}
});

registerAction2(class NewChatEditorToSideAction extends Action2 {
constructor() {
super({
id: 'workbench.action.openChatToSide',
title: localize2('interactiveSession.openToSide', "New Chat Editor to the Side"),
f1: true,
category: CHAT_CATEGORY,
precondition: ChatContextKeys.enabled,
});
}

async run(accessor: ServicesAccessor) {
const widgetService = accessor.get(IChatWidgetService);
await widgetService.openSession(LocalChatSessionUri.getNewSessionUri(), SIDE_GROUP, { pinned: true } satisfies IChatEditorOptions);
}
});

registerAction2(class NewChatWindowAction extends Action2 {
constructor() {
super({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,9 @@ export class OpenModelPickerAction extends Action2 {
group: 'navigation',
when:
ContextKeyExpr.and(
ChatContextKeys.lockedToCodingAgent.negate(),
ContextKeyExpr.or(
ChatContextKeys.lockedToCodingAgent.negate(),
ChatContextKeys.chatSessionHasTargetedModels),
ContextKeyExpr.or(
ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Chat),
ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditorInline),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { CommandsRegistry } from '../../../../../platform/commands/common/comman
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
import { ActiveEditorContext } from '../../../../common/contextkeys.js';
import { IViewsService } from '../../../../services/views/common/viewsService.js';
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { IChatEditingSession } from '../../common/editing/chatEditingService.js';
Expand All @@ -25,7 +24,6 @@ import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common
import { getChatSessionType, LocalChatSessionUri } from '../../common/model/chatUri.js';
import { ChatViewId, IChatWidgetService, isIChatViewViewContext } from '../chat.js';
import { EditingSessionAction, EditingSessionActionContext, getEditingSessionContext } from '../chatEditing/chatEditingActions.js';
import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';
import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';
import { ACTION_ID_NEW_CHAT, ACTION_ID_NEW_EDIT_SESSION, CHAT_CATEGORY, handleCurrentEditingSession } from './chatActions.js';
import { clearChatEditor } from './chatClear.js';
Expand Down Expand Up @@ -115,12 +113,6 @@ export function registerNewChatActions() {
id: MenuId.ChatNewMenu,
group: '1_open',
order: 1,
},
{
id: MenuId.CompactWindowEditorTitle,
group: 'navigation',
when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID),
order: 1
}
],
keybinding: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,8 +614,7 @@ export class DefaultChatAttachmentWidget extends AbstractChatAttachmentWidget {
}

if (attachment.kind === 'symbol') {
const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element));
this._register(this.instantiationService.invokeFunction(hookUpSymbolAttachmentDragAndContextMenu, this.element, scopedContextKeyService, { ...attachment, kind: attachment.symbolKind }, MenuId.ChatInputSymbolAttachmentContext));
this._register(this.instantiationService.invokeFunction(hookUpSymbolAttachmentDragAndContextMenu, this.element, this.contextKeyService, { ...attachment, kind: attachment.symbolKind }, MenuId.ChatInputSymbolAttachmentContext));
}

// Handle click for string context attachments with context commands
Expand Down Expand Up @@ -1115,19 +1114,15 @@ export function hookUpResourceAttachmentDragAndContextMenu(accessor: ServicesAcc
return store;
}

export function hookUpSymbolAttachmentDragAndContextMenu(accessor: ServicesAccessor, widget: HTMLElement, scopedContextKeyService: IScopedContextKeyService, attachment: { name: string; value: Location; kind: SymbolKind }, contextMenuId: MenuId): IDisposable {
export function hookUpSymbolAttachmentDragAndContextMenu(accessor: ServicesAccessor, widget: HTMLElement, parentContextKeyService: IContextKeyService, attachment: { name: string; value: Location; kind: SymbolKind }, contextMenuId: MenuId): IDisposable {
const instantiationService = accessor.get(IInstantiationService);
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const textModelService = accessor.get(ITextModelService);
const contextMenuService = accessor.get(IContextMenuService);
const menuService = accessor.get(IMenuService);

const store = new DisposableStore();

// Context
store.add(setResourceContext(accessor, scopedContextKeyService, attachment.value.uri));

const chatResourceContext = chatAttachmentResourceContextKey.bindTo(scopedContextKeyService);
chatResourceContext.set(attachment.value.uri.toString());

// Drag and drop
widget.draggable = true;
store.add(dom.addDisposableListener(widget, 'dragstart', e => {
Expand All @@ -1143,26 +1138,57 @@ export function hookUpSymbolAttachmentDragAndContextMenu(accessor: ServicesAcces
e.dataTransfer?.setDragImage(widget, 0, 0);
}));

// Context menu
const providerContexts: ReadonlyArray<[IContextKey<boolean>, LanguageFeatureRegistry<unknown>]> = [
[EditorContextKeys.hasDefinitionProvider.bindTo(scopedContextKeyService), languageFeaturesService.definitionProvider],
[EditorContextKeys.hasReferenceProvider.bindTo(scopedContextKeyService), languageFeaturesService.referenceProvider],
[EditorContextKeys.hasImplementationProvider.bindTo(scopedContextKeyService), languageFeaturesService.implementationProvider],
[EditorContextKeys.hasTypeDefinitionProvider.bindTo(scopedContextKeyService), languageFeaturesService.typeDefinitionProvider],
];
// Context menu (context key service created eagerly for keybinding preconditions,
// but resource context and provider contexts are initialized lazily on first use)
const scopedContextKeyService = store.add(parentContextKeyService.createScoped(widget));
chatAttachmentResourceContextKey.bindTo(scopedContextKeyService).set(attachment.value.uri.toString());
store.add(setResourceContext(accessor, scopedContextKeyService, attachment.value.uri));

let providerContexts: ReadonlyArray<[IContextKey<boolean>, LanguageFeatureRegistry<unknown>]> | undefined;

const ensureProviderContexts = () => {
if (!providerContexts) {
providerContexts = [
[EditorContextKeys.hasDefinitionProvider.bindTo(scopedContextKeyService), languageFeaturesService.definitionProvider],
[EditorContextKeys.hasReferenceProvider.bindTo(scopedContextKeyService), languageFeaturesService.referenceProvider],
[EditorContextKeys.hasImplementationProvider.bindTo(scopedContextKeyService), languageFeaturesService.implementationProvider],
[EditorContextKeys.hasTypeDefinitionProvider.bindTo(scopedContextKeyService), languageFeaturesService.typeDefinitionProvider],
];
}
};

const updateContextKeys = async () => {
ensureProviderContexts();
const modelRef = await textModelService.createModelReference(attachment.value.uri);
try {
const model = modelRef.object.textEditorModel;
for (const [contextKey, registry] of providerContexts) {
for (const [contextKey, registry] of providerContexts!) {
contextKey.set(registry.has(model));
}
} finally {
modelRef.dispose();
}
};
store.add(addBasicContextMenu(accessor, widget, scopedContextKeyService, contextMenuId, attachment.value, updateContextKeys));

store.add(dom.addDisposableListener(widget, dom.EventType.CONTEXT_MENU, async domEvent => {
const event = new StandardMouseEvent(dom.getWindow(domEvent), domEvent);
dom.EventHelper.stop(domEvent, true);

try {
await updateContextKeys();
} catch (e) {
console.error(e);
}

contextMenuService.showContextMenu({
contextKeyService: scopedContextKeyService,
getAnchor: () => event,
getActions: () => {
const menu = menuService.getMenuActions(contextMenuId, scopedContextKeyService, { arg: attachment.value });
return getFlatContextMenuActions(menu);
},
});
}));

return store;
}
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ export interface IChatWidgetService {
*/
readonly onDidChangeFocusedWidget: Event<IChatWidget | undefined>;

/**
* Fires when the focused chat session changes, either because the focused widget
* changed or because the focused widget loaded a different session.
*/
readonly onDidChangeFocusedSession: Event<void>;

/**
* Reveals the widget, focusing its input unless `preserveFocus` is true.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint<IChatSessionsEx
customAgentTarget: {
description: localize('chatSessionsExtPoint.customAgentTarget', 'When set, the chat session will show a filtered mode picker that prefers custom agents whose target property matches this value. Custom agents without a target property are still shown in all session types. This enables the use of standard agent/mode with contributed sessions.'),
type: 'string'
},
requiresCustomModels: {
description: localize('chatSessionsExtPoint.requiresCustomModels', 'When set, the chat session will show a filtered model picker that prefers custom models. This enables the use of standard model picker with contributed sessions.'),
type: 'boolean',
default: false
}
},
required: ['type', 'name', 'displayName', 'description'],
Expand Down Expand Up @@ -1122,6 +1127,11 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
return contribution?.customAgentTarget ?? Target.Undefined;
}

public requiresCustomModelsForSessionType(chatSessionType: string): boolean {
const contribution = this._contributions.get(chatSessionType)?.contribution;
return !!contribution?.requiresCustomModels;
}

public getContentProviderSchemes(): string[] {
return Array.from(this._contentProviders.keys());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation {

progress({
kind: 'progressMessage',
content: new MarkdownString(localize('waitingChat', "Getting chat ready...")),
content: new MarkdownString(localize('waitingChat', "Getting chat ready")),
shimmer: true,
});

await this.forwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);
Expand Down Expand Up @@ -319,7 +320,8 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation {
const timeoutHandle = setTimeout(() => {
progress({
kind: 'progressMessage',
content: new MarkdownString(localize('waitingChat2', "Chat is almost ready...")),
content: new MarkdownString(localize('waitingChat2', "Chat is almost ready")),
shimmer: true,
});
}, 10000);

Expand Down Expand Up @@ -602,13 +604,15 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation {
case ChatSetupStep.SigningIn:
progress({
kind: 'progressMessage',
content: new MarkdownString(localize('setupChatSignIn2', "Signing in to {0}...", defaultAccountService.getDefaultAccountAuthenticationProvider().name)),
content: new MarkdownString(localize('setupChatSignIn2', "Signing in to {0}", defaultAccountService.getDefaultAccountAuthenticationProvider().name)),
shimmer: true,
});
break;
case ChatSetupStep.Installing:
progress({
kind: 'progressMessage',
content: new MarkdownString(localize('installingChat', "Getting chat ready...")),
content: new MarkdownString(localize('installingChat', "Getting chat ready")),
shimmer: true,
});
break;
}
Expand Down
Loading
Loading