Path: blob/main/src/vs/workbench/contrib/chat/browser/chat.ts
5251 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IMouseWheelEvent } from '../../../../base/browser/mouseEvent.js';6import { Event } from '../../../../base/common/event.js';7import { IDisposable } from '../../../../base/common/lifecycle.js';8import { URI } from '../../../../base/common/uri.js';9import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';10import { Selection } from '../../../../editor/common/core/selection.js';11import { EditDeltaInfo } from '../../../../editor/common/textModelEditSource.js';12import { MenuId } from '../../../../platform/actions/common/actions.js';13import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';14import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';15import { PreferredGroup } from '../../../services/editor/common/editorService.js';16import { IChatAgentAttachmentCapabilities, IChatAgentCommand, IChatAgentData } from '../common/participants/chatAgents.js';17import { IChatResponseModel, IChatModelInputState } from '../common/model/chatModel.js';18import { IChatMode } from '../common/chatModes.js';19import { IParsedChatRequest } from '../common/requestParser/chatParserTypes.js';20import { CHAT_PROVIDER_ID } from '../common/participants/chatParticipantContribTypes.js';21import { ChatRequestQueueKind, IChatElicitationRequest, IChatLocationData, IChatSendRequestOptions } from '../common/chatService/chatService.js';22import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatPendingDividerViewModel } from '../common/model/chatViewModel.js';23import { ChatAgentLocation, ChatModeKind } from '../common/constants.js';24import { ChatAttachmentModel } from './attachments/chatAttachmentModel.js';25import { IChatEditorOptions } from './widgetHosts/editor/chatEditor.js';26import { ChatInputPart } from './widget/input/chatInputPart.js';27import { ChatWidget, IChatWidgetContrib } from './widget/chatWidget.js';28import { ICodeBlockActionContext } from './widget/chatContentParts/codeBlockPart.js';29import { AgentSessionProviders } from './agentSessions/agentSessions.js';3031/**32* A workspace item that can be selected in the workspace picker.33*/34export interface IWorkspacePickerItem {35readonly uri: URI;36readonly label: string;37readonly isFolder: boolean;38}3940/**41* Delegate interface for the workspace picker.42* Allows consumers to get and set the target workspace for chat submissions in empty window contexts.43*/44export interface IWorkspacePickerDelegate {45/**46* Returns the list of available workspaces to select from.47*/48getWorkspaces(): IWorkspacePickerItem[];49/**50* Returns the currently selected workspace, if any.51*/52getSelectedWorkspace(): IWorkspacePickerItem | undefined;53/**54* Sets the currently selected workspace.55*/56setSelectedWorkspace(workspace: IWorkspacePickerItem | undefined): void;57/**58* Event that fires when the selected workspace changes.59*/60onDidChangeSelectedWorkspace: Event<IWorkspacePickerItem | undefined>;61/**62* Event that fires when the available workspaces change.63*/64onDidChangeWorkspaces: Event<void>;65/**66* Command ID to execute when user wants to open a new folder.67*/68openFolderCommand: string;69}7071/**72* Delegate interface for the session target picker.73* Allows consumers to get and optionally set the active session provider.74*/75export interface ISessionTypePickerDelegate {76getActiveSessionProvider(): AgentSessionProviders | undefined;77/**78* Optional setter for the active session provider.79* When provided, the picker will call this instead of executing the openNewChatSessionInPlace command.80* This allows the welcome view to maintain independent state from the main chat panel.81*/82setActiveSessionProvider?(provider: AgentSessionProviders): void;83/**84* Optional getter for the pending delegation target - the target that will be used when submit is pressed.85*/86getPendingDelegationTarget?(): AgentSessionProviders | undefined;87/**88* Optional setter for the pending delegation target.89* When a user selects a different session provider in a non-empty chat,90* this stores the target for delegation on the next submit instead of immediately creating a new session.91*/92setPendingDelegationTarget?(provider: AgentSessionProviders): void;93/**94* Optional event that fires when the active session provider changes.95* When provided, listeners (like chatInputPart) can react to session type changes96* and update pickers accordingly.97*/98onDidChangeActiveSessionProvider?: Event<AgentSessionProviders>;99}100101export const IChatWidgetService = createDecorator<IChatWidgetService>('chatWidgetService');102103export interface IChatWidgetService {104105readonly _serviceBrand: undefined;106107/**108* Returns the most recently focused widget if any.109*110* ⚠️ Consider carefully if this is appropriate for your use case. If you111* can know what session you're interacting with, prefer {@link getWidgetBySessionResource}112* or similar methods to work nicely with multiple chat widgets.113*/114readonly lastFocusedWidget: IChatWidget | undefined;115116readonly onDidAddWidget: Event<IChatWidget>;117118/**119* Fires when a chat session is no longer open in any chat widget.120*/121readonly onDidBackgroundSession: Event<URI>;122123/**124* Reveals the widget, focusing its input unless `preserveFocus` is true.125*/126reveal(widget: IChatWidget, preserveFocus?: boolean): Promise<boolean>;127128/**129* Reveals the last active widget, or creates a new chat if necessary.130*/131revealWidget(preserveFocus?: boolean): Promise<IChatWidget | undefined>;132133getAllWidgets(): ReadonlyArray<IChatWidget>;134getWidgetByInputUri(uri: URI): IChatWidget | undefined;135openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget): Promise<IChatWidget | undefined>;136openSession(sessionResource: URI, target?: PreferredGroup, options?: IChatEditorOptions): Promise<IChatWidget | undefined>;137openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget | PreferredGroup, options?: IChatEditorOptions): Promise<IChatWidget | undefined>;138139getWidgetBySessionResource(sessionResource: URI): IChatWidget | undefined;140141getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray<IChatWidget>;142143/**144* An IChatWidget registers itself when created.145*/146register(newWidget: IChatWidget): IDisposable;147}148149export const ChatViewPaneTarget = Symbol('ChatViewPaneTarget');150151export const IQuickChatService = createDecorator<IQuickChatService>('quickChatService');152export interface IQuickChatService {153readonly _serviceBrand: undefined;154readonly onDidClose: Event<void>;155readonly enabled: boolean;156readonly focused: boolean;157/** Defined when quick chat is open */158readonly sessionResource?: URI;159toggle(options?: IQuickChatOpenOptions): void;160focus(): void;161open(options?: IQuickChatOpenOptions): void;162close(): void;163openInChatView(): void;164}165166export interface IQuickChatOpenOptions {167/**168* The query for quick chat.169*/170query: string;171/**172* Whether the query is partial and will await more input from the user.173*/174isPartialQuery?: boolean;175/**176* An optional selection range to apply to the query text box.177*/178selection?: Selection;179}180181export const IChatAccessibilityService = createDecorator<IChatAccessibilityService>('chatAccessibilityService');182export interface IChatAccessibilityService {183readonly _serviceBrand: undefined;184acceptRequest(uri: URI, skipRequestSignal?: boolean): void;185disposeRequest(requestId: URI): void;186acceptResponse(widget: ChatWidget, container: HTMLElement, response: IChatResponseViewModel | string | undefined, requestId: URI | undefined, isVoiceInput?: boolean): void;187acceptElicitation(message: IChatElicitationRequest): void;188}189190export interface IChatCodeBlockInfo {191readonly ownerMarkdownPartId: string;192readonly codeBlockIndex: number;193readonly elementId: string;194readonly uri: URI | undefined;195readonly uriPromise: Promise<URI | undefined>;196codemapperUri: URI | undefined;197readonly chatSessionResource: URI | undefined;198focus(): void;199readonly languageId?: string | undefined;200readonly editDeltaInfo?: EditDeltaInfo | undefined;201}202203export interface IChatFileTreeInfo {204treeDataId: string;205treeIndex: number;206focus(): void;207}208209export type ChatTreeItem = IChatRequestViewModel | IChatResponseViewModel | IChatPendingDividerViewModel;210211export interface IChatListItemRendererOptions {212readonly renderStyle?: 'compact' | 'minimal';213readonly noHeader?: boolean;214readonly noFooter?: boolean;215readonly renderDetectedCommandsWithRequest?: boolean;216readonly restorable?: boolean;217readonly editable?: boolean;218readonly renderTextEditsAsSummary?: (uri: URI) => boolean;219readonly referencesExpandedWhenEmptyResponse?: boolean | ((mode: ChatModeKind) => boolean);220readonly progressMessageAtBottomOfResponse?: boolean | ((mode: ChatModeKind) => boolean);221}222223export interface IChatWidgetViewOptions {224autoScroll?: boolean | ((mode: ChatModeKind) => boolean);225renderInputOnTop?: boolean;226renderFollowups?: boolean;227renderStyle?: 'compact' | 'minimal';228renderInputToolbarBelowInput?: boolean;229supportsFileReferences?: boolean;230filter?: (item: ChatTreeItem) => boolean;231/** Action triggered when 'clear' is called on the widget. */232clear?: () => Promise<void>;233rendererOptions?: IChatListItemRendererOptions;234menus?: {235/**236* The menu that is inside the input editor, use for send, dictation237*/238executeToolbar?: MenuId;239/**240* The menu that next to the input editor, use for close, config etc241*/242inputSideToolbar?: MenuId;243/**244* The telemetry source for all commands of this widget245*/246telemetrySource?: string;247};248defaultElementHeight?: number;249editorOverflowWidgetsDomNode?: HTMLElement;250enableImplicitContext?: boolean;251enableWorkingSet?: 'explicit' | 'implicit';252supportsChangingModes?: boolean;253dndContainer?: HTMLElement;254defaultMode?: IChatMode;255/**256* Optional delegate for the session target picker.257* When provided, allows the widget to maintain independent state for the selected session type.258* This is useful for contexts like the welcome view where target selection should not259* immediately open a new session.260*/261sessionTypePickerDelegate?: ISessionTypePickerDelegate;262263/**264* Optional delegate for the workspace picker.265* When provided, shows a workspace picker in the chat input allowing users to select266* a target workspace for their request. This is useful for empty window contexts where267* the user wants to send a request to a specific workspace.268*/269workspacePickerDelegate?: IWorkspacePickerDelegate;270271/**272* Optional handler for chat submission.273* When provided, this handler is called before the normal input acceptance flow.274* If it returns true (handled), the normal submission is skipped.275* This is useful for contexts like the welcome view where submission should276* redirect to a different workspace rather than executing locally.277*/278submitHandler?: (query: string, mode: ChatModeKind) => Promise<boolean>;279}280281export interface IChatViewViewContext {282viewId: string;283}284285export function isIChatViewViewContext(context: IChatWidgetViewContext): context is IChatViewViewContext {286return typeof (context as IChatViewViewContext).viewId === 'string';287}288289export interface IChatResourceViewContext {290isQuickChat?: boolean;291isInlineChat?: boolean;292}293294export function isIChatResourceViewContext(context: IChatWidgetViewContext): context is IChatResourceViewContext {295return !isIChatViewViewContext(context);296}297298export type IChatWidgetViewContext = IChatViewViewContext | IChatResourceViewContext | {};299300export interface IChatAcceptInputOptions {301noCommandDetection?: boolean;302isVoiceInput?: boolean;303enableImplicitContext?: boolean; // defaults to true304// Whether to store the input to history. This defaults to 'true' if the input305// box's current content is being accepted, or 'false' if a specific input306// is being submitted to the widget.307storeToHistory?: boolean;308/**309* When set, queues this message to be sent after the current request completes.310* If Steering, also sets yieldRequested on any active request to signal it should wrap up.311*/312queue?: ChatRequestQueueKind;313}314315export interface IChatWidgetViewModelChangeEvent {316readonly previousSessionResource: URI | undefined;317readonly currentSessionResource: URI | undefined;318}319320export interface IChatWidget {321readonly domNode: HTMLElement;322readonly onDidChangeViewModel: Event<IChatWidgetViewModelChangeEvent>;323readonly onDidAcceptInput: Event<void>;324readonly onDidHide: Event<void>;325readonly onDidShow: Event<void>;326readonly onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>;327readonly onDidChangeAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>;328readonly onDidChangeParsedInput: Event<void>;329readonly onDidFocus: Event<void>;330readonly location: ChatAgentLocation;331readonly viewContext: IChatWidgetViewContext;332readonly viewModel: IChatViewModel | undefined;333readonly inputEditor: ICodeEditor;334readonly supportsFileReferences: boolean;335readonly attachmentCapabilities: IChatAgentAttachmentCapabilities;336readonly parsedInput: IParsedChatRequest;337readonly lockedAgentId: string | undefined;338lastSelectedAgent: IChatAgentData | undefined;339readonly scopedContextKeyService: IContextKeyService;340readonly input: ChatInputPart;341readonly attachmentModel: ChatAttachmentModel;342readonly locationData?: IChatLocationData;343readonly contribs: readonly IChatWidgetContrib[];344345readonly supportsChangingModes: boolean;346347getContrib<T extends IChatWidgetContrib>(id: string): T | undefined;348reveal(item: ChatTreeItem): void;349focus(item: ChatTreeItem): void;350getSibling(item: ChatTreeItem, type: 'next' | 'previous'): ChatTreeItem | undefined;351getFocus(): ChatTreeItem | undefined;352setInput(query?: string): void;353getInput(): string;354refreshParsedInput(): void;355logInputHistory(): void;356acceptInput(query?: string, options?: IChatAcceptInputOptions): Promise<IChatResponseModel | undefined>;357startEditing(requestId: string): void;358finishedEditing(completedEdit?: boolean): void;359rerunLastRequest(): Promise<void>;360setInputPlaceholder(placeholder: string): void;361resetInputPlaceholder(): void;362/**363* Focuses the response item in the list.364* @param lastFocused Focuses the most recently focused response. Otherwise, focuses the last response.365*/366focusResponseItem(lastFocused?: boolean): void;367focusInput(): void;368/**369* Focuses the Todos view in the chat widget.370* @returns Whether the operation succeeded (i.e., the Todos view was focused).371*/372focusTodosView(): boolean;373/**374* Toggles focus between the Todos view and the previous focus target in the chat widget.375* @returns Whether the operation succeeded (i.e., the focus was toggled).376*/377toggleTodosViewFocus(): boolean;378/**379* Focuses the question carousel in the chat widget.380* @returns Whether the operation succeeded (i.e., the question carousel was focused).381*/382focusQuestionCarousel(): boolean;383/**384* Toggles focus between the question carousel and the chat input.385* @returns Whether the operation succeeded (i.e., the focus was toggled).386*/387toggleQuestionCarouselFocus(): boolean;388hasInputFocus(): boolean;389getModeRequestOptions(): Partial<IChatSendRequestOptions>;390getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined;391getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[];392getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[];393getLastFocusedFileTreeForResponse(response: IChatResponseViewModel): IChatFileTreeInfo | undefined;394clear(): Promise<void>;395getViewState(): IChatModelInputState | undefined;396lockToCodingAgent(name: string, displayName: string, agentId?: string): void;397unlockFromCodingAgent(): void;398handleDelegationExitIfNeeded(sourceAgent: Pick<IChatAgentData, 'id' | 'name'> | undefined, targetAgent: IChatAgentData | undefined): Promise<void>;399400delegateScrollFromMouseWheelEvent(event: IMouseWheelEvent): void;401}402403404export interface ICodeBlockActionContextProvider {405getCodeBlockContext(editor?: ICodeEditor): ICodeBlockActionContext | undefined;406}407408export const IChatCodeBlockContextProviderService = createDecorator<IChatCodeBlockContextProviderService>('chatCodeBlockContextProviderService');409export interface IChatCodeBlockContextProviderService {410readonly _serviceBrand: undefined;411readonly providers: ICodeBlockActionContextProvider[];412registerProvider(provider: ICodeBlockActionContextProvider, id: string): IDisposable;413}414415export const ChatViewId = `workbench.panel.chat.view.${CHAT_PROVIDER_ID}`;416export const ChatViewContainerId = 'workbench.panel.chat';417418419