diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index fab89ecfe3c..b047830a881 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -22,7 +22,6 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IContentActionHandler, renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { IInlineChatService, IInlineChatSessionProvider } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -36,6 +35,7 @@ import { LOG_MODE_ID, OUTPUT_MODE_ID } from 'vs/workbench/services/output/common import { SEARCH_RESULT_LANGUAGE_ID } from 'vs/workbench/services/search/common/search'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { ChatAgentLocation, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; const $ = dom.$; @@ -76,7 +76,7 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { @IHoverService protected readonly hoverService: IHoverService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IInlineChatSessionService private readonly inlineChatSessionService: IInlineChatSessionService, - @IInlineChatService protected readonly inlineChatService: IInlineChatService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IProductService protected readonly productService: IProductService, ) { @@ -84,7 +84,7 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { this.toDispose.push(this.editor.onDidChangeModel(() => this.update())); this.toDispose.push(this.editor.onDidChangeModelLanguage(() => this.update())); this.toDispose.push(this.editor.onDidChangeModelContent(() => this.update())); - this.toDispose.push(this.inlineChatService.onDidChangeProviders(() => this.update())); + this.toDispose.push(this.chatAgentService.onDidChangeAgents(() => this.update())); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.update())); this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.readOnly)) { @@ -146,9 +146,9 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { return false; } - const inlineChatProviders = [...this.inlineChatService.getAllProvider()]; - const shouldRenderDefaultHint = model?.uri.scheme === Schemas.untitled && languageId === PLAINTEXT_LANGUAGE_ID && !inlineChatProviders.length; - return inlineChatProviders.length > 0 || shouldRenderDefaultHint; + const hasEditorAgents = Boolean(this.chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)); + const shouldRenderDefaultHint = model?.uri.scheme === Schemas.untitled && languageId === PLAINTEXT_LANGUAGE_ID && hasEditorAgents; + return hasEditorAgents || shouldRenderDefaultHint; } protected update(): void { @@ -162,7 +162,7 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { this.configurationService, this.hoverService, this.keybindingService, - this.inlineChatService, + this.chatAgentService, this.telemetryService, this.productService ); @@ -195,7 +195,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { private readonly configurationService: IConfigurationService, private readonly hoverService: IHoverService, private readonly keybindingService: IKeybindingService, - private readonly inlineChatService: IInlineChatService, + private readonly chatAgentService: IChatAgentService, private readonly telemetryService: ITelemetryService, private readonly productService: IProductService ) { @@ -218,8 +218,8 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { return EmptyTextEditorHintContentWidget.ID; } - private _getHintInlineChat(providers: IInlineChatSessionProvider[]) { - const providerName = (providers.length === 1 ? providers[0].label : undefined) ?? this.productService.nameShort; + private _getHintInlineChat(providers: IChatAgent[]) { + const providerName = (providers.length === 1 ? providers[0].fullName : undefined) ?? this.productService.nameShort; const inlineChatId = 'inlineChat.start'; let ariaLabel = `Ask ${providerName} something or start typing to dismiss.`; @@ -399,7 +399,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { this.domNode.style.width = 'max-content'; this.domNode.style.paddingLeft = '4px'; - const inlineChatProviders = [...this.inlineChatService.getAllProvider()]; + const inlineChatProviders = this.chatAgentService.getActivatedAgents().filter(candidate => candidate.locations.includes(ChatAgentLocation.Editor)); const { hintElement, ariaLabel } = !inlineChatProviders.length ? this._getHintDefault() : this._getHintInlineChat(inlineChatProviders); this.domNode.append(hintElement); this.ariaLabel = ariaLabel.concat(localize('disableHint', ' Toggle {0} in settings to disable this hint.', AccessibilityVerbositySettingId.EmptyEditorHint)); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 2044d45b2e3..2edb4d039c5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -13,24 +13,23 @@ import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/in import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { InlineChatNotebookContribution } from 'vs/workbench/contrib/inlineChat/browser/inlineChatNotebook'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, registerWorkbenchContribution2, Extensions as WorkbenchExtensions, WorkbenchPhase } from 'vs/workbench/common/contributions'; import { InlineChatSavingServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingServiceImpl'; import { InlineChatAccessibleView } from 'vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView'; import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; +import { InlineChatEnabler, InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; // --- browser registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); -registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Eager); // EAGER because this registers an agent which we need swiftly +registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Delayed); registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, InstantiationType.Delayed); registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors -AccessibleViewRegistry.register(new InlineChatAccessibleView()); registerAction2(InlineChatActions.StartSessionAction); registerAction2(InlineChatActions.CloseAction); @@ -58,4 +57,6 @@ registerAction2(InlineChatActions.CopyRecordings); const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatNotebookContribution, LifecyclePhase.Restored); +registerWorkbenchContribution2(InlineChatEnabler.Id, InlineChatEnabler, WorkbenchPhase.AfterRestored); + AccessibleViewRegistry.register(new InlineChatAccessibleView()); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 1a1048c1db5..bac8e7419f3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as aria from 'vs/base/browser/ui/aria/aria'; -import { Barrier, DeferredPromise, Queue, raceCancellation } from 'vs/base/common/async'; +import { Barrier, DeferredPromise, Queue } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -38,7 +38,6 @@ import { IInlineChatSessionService } from './inlineChatSessionService'; import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { InlineChatZoneWidget } from './inlineChatZoneWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { StashedSession } from './inlineChatSession'; import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; @@ -147,7 +146,6 @@ export class InlineChatController implements IEditorContribution { @IContextKeyService contextKeyService: IContextKeyService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IChatService private readonly _chatService: IChatService, - @ICommandService private readonly _commandService: ICommandService, @ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService, @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, ) { @@ -442,7 +440,7 @@ export class InlineChatController implements IEditorContribution { })); // Update context key - this._ctxSupportIssueReporting.set(this._session.provider.supportIssueReporting ?? false); + this._ctxSupportIssueReporting.set(this._session.agent.metadata.supportIssueReporting ?? false); // #region DEBT // DEBT@jrieken @@ -616,7 +614,7 @@ export class InlineChatController implements IEditorContribution { return false; }); if (refer && slashCommandLike && !this._session.lastExchange) { - this._log('[IE] seeing refer command, continuing outside editor', this._session.provider.extensionId); + this._log('[IE] seeing refer command, continuing outside editor', this._session.agent.extensionId); // cancel this request this._chatService.cancelCurrentRequestForSession(request.session.sessionId); @@ -811,31 +809,6 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.updateToolbar(true); newPosition = await this._strategy.renderChanges(response); - - if (this._session.provider.provideFollowups) { - const followupCts = new CancellationTokenSource(); - const msgListener = Event.once(this._messages.event)(() => { - followupCts.cancel(); - }); - const followupTask = this._session.provider.provideFollowups(this._session.session, response.raw, followupCts.token); - this._log('followup request started', this._session.provider.extensionId, this._session.session, response.raw); - raceCancellation(Promise.resolve(followupTask), followupCts.token).then(followupReply => { - if (followupReply && this._session) { - this._log('followup request received', this._session.provider.extensionId, this._session.session, followupReply); - this._zone.value.widget.updateFollowUps(followupReply, followup => { - if (followup.kind === 'reply') { - this.updateInput(followup.message); - this.acceptInput(); - } else { - this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); - } - }); - } - }).finally(() => { - msgListener.dispose(); - followupCts.dispose(); - }); - } } this._showWidget(false, newPosition); @@ -982,7 +955,7 @@ export class InlineChatController implements IEditorContribution { assertType(this._strategy); const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._session.textModelN.uri, edits); - this._log('edits from PROVIDER and after making them MORE MINIMAL', this._session.provider.extensionId, edits, moreMinimalEdits); + this._log('edits from PROVIDER and after making them MORE MINIMAL', this._session.agent.extensionId, edits, moreMinimalEdits); if (moreMinimalEdits?.length === 0) { // nothing left to do diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 6a6d05d7454..2bebeaede34 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { IWorkspaceTextEdit, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -34,6 +34,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ILogService } from 'vs/platform/log/common/log'; import { ChatModel, IChatRequestModel, IChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IChatAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; export type TelemetryData = { @@ -160,7 +161,7 @@ export class Session { * The document into which AI edits went, when live this is `targetUri` otherwise it is a temporary document */ readonly textModelN: ITextModel, - readonly provider: IInlineChatSessionProvider, + readonly agent: IChatAgent, readonly session: IInlineChatSession, readonly wholeRange: SessionWholeRange, readonly hunkData: HunkData, @@ -168,7 +169,7 @@ export class Session { ) { this.textModelNAltVersion = textModelN.getAlternativeVersionId(); this._teldata = { - extension: ExtensionIdentifier.toKey(provider.extensionId), + extension: ExtensionIdentifier.toKey(agent.extensionId), startTime: this._startTime.toISOString(), endTime: this._startTime.toISOString(), edits: 0, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 5f1e3ac6b3d..7e77096a55c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -2,216 +2,36 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesceInPlace, isNonEmptyArray } from 'vs/base/common/arrays'; -import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Iterable } from 'vs/base/common/iterator'; -import { DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { LRUCache } from 'vs/base/common/map'; +import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; -import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; +import { IValidEditOperation } from 'vs/editor/common/model'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; -import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatFollowup, IChatProgress, IChatService, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { EditMode, IInlineChatBulkEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, IInlineChatService, IInlineChatSession, IInlineChatSessionProvider, IInlineChatSlashCommand, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { CTX_INLINE_CHAT_HAS_PROVIDER, EditMode, IInlineChatBulkEditResponse, IInlineChatSession, IInlineChatSlashCommand, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; import { IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer, Recording } from './inlineChatSessionService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { ISelection } from 'vs/editor/common/core/selection'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { Codicon } from 'vs/base/common/codicons'; -import { isEqual } from 'vs/base/common/resources'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -class BridgeAgent implements IChatAgentImplementation { - - constructor( - private readonly _data: IChatAgentData, - private readonly _sessions: ReadonlyMap, - private readonly _postLastResponse: (data: { id: string; response: ReplyResponse | ErrorResponse | EmptyResponse }) => void, - @IInstantiationService private readonly _instaService: IInstantiationService, - ) { } - - - private _findSessionDataByRequest(request: IChatAgentRequest) { - let data: SessionData | undefined; - for (const candidate of this._sessions.values()) { - if (candidate.session.chatModel.sessionId === request.sessionId) { - data = candidate; - break; - } - } - return data; - } - - async invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, _history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { - - if (token.isCancellationRequested) { - return {}; - } - - const data = this._findSessionDataByRequest(request); - - if (!data) { - throw new Error('FAILED to find session'); - } - - const { session } = data; - - if (!session.lastInput) { - throw new Error('FAILED to find last input'); - } - - const inlineChatContextValue = request.variables.variables.find(candidate => candidate.name === _inlineChatContext)?.value; - const inlineChatContext = typeof inlineChatContextValue === 'string' && JSON.parse(inlineChatContextValue); - - const modelAltVersionIdNow = session.textModelN.getAlternativeVersionId(); - const progressEdits: TextEdit[][] = []; - - const inlineRequest: IInlineChatRequest = { - requestId: request.requestId, - prompt: request.message, - attempt: request.attempt ?? 0, - withIntentDetection: request.enableCommandDetection ?? true, - live: session.editMode !== EditMode.Preview, - previewDocument: session.textModelN.uri, - selection: inlineChatContext.selection, - wholeRange: inlineChatContext.wholeRange - }; - - const inlineProgress = new Progress(data => { - // TODO@jrieken - // if (data.message) { - // progress({ kind: 'progressMessage', content: new MarkdownString(data.message) }); - // } - // TODO@ulugbekna,jrieken should we only send data.slashCommand when having detected one? - if (data.slashCommand && !inlineRequest.prompt.startsWith('/')) { - const command = this._data.slashCommands.find(c => c.name === data.slashCommand); - progress({ kind: 'agentDetection', agentId: this._data.id, command }); - } - if (data.markdownFragment) { - progress({ kind: 'markdownContent', content: new MarkdownString(data.markdownFragment) }); - } - if (isNonEmptyArray(data.edits)) { - progressEdits.push(data.edits); - progress({ kind: 'textEdit', uri: session.textModelN.uri, edits: data.edits }); - } - }); - - let result: IInlineChatResponse | undefined | null; - let response: ReplyResponse | ErrorResponse | EmptyResponse; - - try { - result = await data.session.provider.provideResponse(session.session, inlineRequest, inlineProgress, token); - - if (result) { - if (result.message) { - inlineProgress.report({ markdownFragment: result.message.value }); - } - if (Array.isArray(result.edits)) { - inlineProgress.report({ edits: result.edits }); - } - - const markdownContents = result.message ?? new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }); - - const chatModelRequest = session.chatModel.getRequests().find(candidate => candidate.id === request.requestId); - - response = this._instaService.createInstance(ReplyResponse, result, markdownContents, session.textModelN.uri, modelAltVersionIdNow, progressEdits, request.requestId, chatModelRequest?.response); - - } else { - response = new EmptyResponse(); - } - - } catch (e) { - response = new ErrorResponse(e); - } - - this._postLastResponse({ id: request.requestId, response }); - - - return { - metadata: { - inlineChatResponse: result - } - }; - } - - async provideFollowups(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { - - if (!result.metadata?.inlineChatResponse) { - return []; - } - - const data = this._findSessionDataByRequest(request); - if (!data) { - return []; - } - - const inlineFollowups = await data.session.provider.provideFollowups?.(data.session.session, result.metadata?.inlineChatResponse, token); - if (!inlineFollowups) { - return []; - } - - const chatFollowups = inlineFollowups.map(f => { - if (f.kind === 'reply') { - return { - kind: 'reply', - message: f.message, - agentId: request.agentId, - title: f.title, - tooltip: f.tooltip, - } satisfies IChatFollowup; - } else { - // TODO@jrieken update API - return undefined; - } - }); - - coalesceInPlace(chatFollowups); - return chatFollowups; - } - - provideWelcomeMessage(location: ChatAgentLocation, token: CancellationToken): string[] { - // without this provideSampleQuestions is not called - return []; - } - - async provideSampleQuestions(location: ChatAgentLocation, token: CancellationToken): Promise { - // TODO@jrieken DEBT - // (hack) this function is called while creating the session. We need the timeout to make sure this._sessions is populated. - // (hack) we have no context/session id and therefore use the first session with an active editor - await new Promise(resolve => setTimeout(resolve, 10)); - - for (const [, data] of this._sessions) { - if (data.session.session.input && data.editor.hasWidgetFocus()) { - return [{ - kind: 'reply', - agentId: _bridgeAgentId, - message: data.session.session.input, - }]; - } - } - return []; - } -} type SessionData = { editor: ICodeEditor; @@ -227,7 +47,6 @@ export class InlineChatError extends Error { } } -const _bridgeAgentId = 'brigde.editor'; const _inlineChatContext = '_inlineChatContext'; const _inlineChatDocument = '_inlineChatDocument'; @@ -264,10 +83,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { private readonly _keyComputers = new Map(); private _recordings: Recording[] = []; - private readonly _lastResponsesFromBridgeAgent = new LRUCache(5); constructor( - @IInlineChatService private readonly _inlineChatService: IInlineChatService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IModelService private readonly _modelService: IModelService, @ITextModelService private readonly _textModelService: ITextModelService, @@ -280,103 +97,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { @IChatVariablesService chatVariableService: IChatVariablesService, ) { - const fakeProviders = this._store.add(new DisposableMap()); - - this._store.add(this._chatAgentService.onDidChangeAgents(() => { - - const providersNow = new Set(); - - for (const agent of this._chatAgentService.getActivatedAgents()) { - if (agent.id === _bridgeAgentId) { - // not interesting - continue; - } - if (!agent.locations.includes(ChatAgentLocation.Editor) || !agent.isDefault) { - // not interesting - continue; - } - providersNow.add(agent.id); - - if (!fakeProviders.has(agent.id)) { - fakeProviders.set(agent.id, _inlineChatService.addProvider(_instaService.createInstance(AgentInlineChatProvider, agent))); - this._logService.debug(`ADDED inline chat provider for agent ${agent.id}`); - } - } - - for (const [id] of fakeProviders) { - if (!providersNow.has(id)) { - fakeProviders.deleteAndDispose(id); - this._logService.debug(`REMOVED inline chat provider for agent ${id}`); - } - } - })); - - // MARK: register fake chat agent - const addOrRemoveBridgeAgent = () => { - const that = this; - const agentData: IChatAgentData = { - id: _bridgeAgentId, - name: 'editor', - extensionId: nullExtensionDescription.identifier, - publisherDisplayName: '', - extensionDisplayName: '', - extensionPublisherId: '', - isDefault: true, - locations: [ChatAgentLocation.Editor], - get slashCommands(): IChatAgentCommand[] { - // HACK@jrieken - // find the active session and return its slash commands - let candidate: Session | undefined; - for (const data of that._sessions.values()) { - if (data.editor.hasWidgetFocus()) { - candidate = data.session; - break; - } - } - if (!candidate || !candidate.session.slashCommands) { - return []; - } - return candidate.session.slashCommands.map(c => { - return { - name: c.command, - description: c.detail ?? '', - } satisfies IChatAgentCommand; - }); - }, - defaultImplicitVariables: [_inlineChatContext], - metadata: { - isSticky: false, - themeIcon: Codicon.copilot, - }, - }; - - let otherEditorAgent: IChatAgentData | undefined; - let myEditorAgent: IChatAgentData | undefined; - - for (const candidate of this._chatAgentService.getActivatedAgents()) { - if (!myEditorAgent && candidate.id === agentData.id) { - myEditorAgent = candidate; - } else if (!otherEditorAgent && candidate.isDefault && candidate.locations.includes(ChatAgentLocation.Editor)) { - otherEditorAgent = candidate; - } - } - - if (otherEditorAgent) { - bridgeStore.clear(); - _logService.debug(`REMOVED bridge agent "${agentData.id}", found "${otherEditorAgent.id}"`); - - } else if (!myEditorAgent) { - bridgeStore.value = this._chatAgentService.registerDynamicAgent(agentData, this._instaService.createInstance(BridgeAgent, agentData, this._sessions, data => { - this._lastResponsesFromBridgeAgent.set(data.id, data.response); - })); - _logService.debug(`ADDED bridge agent "${agentData.id}"`); - } - }; - - this._store.add(this._chatAgentService.onDidChangeAgents(() => addOrRemoveBridgeAgent())); - const bridgeStore = this._store.add(new MutableDisposable()); - addOrRemoveBridgeAgent(); - // MARK: implicit variable for editor selection and (tracked) whole range @@ -414,47 +134,34 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise { const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Editor); - let provider: IInlineChatSessionProvider | undefined; - if (agent) { - for (const candidate of this._inlineChatService.getAllProvider()) { - if (candidate instanceof AgentInlineChatProvider && candidate.agent === agent) { - provider = candidate; - break; - } - } - } - if (!provider) { - provider = Iterable.first(this._inlineChatService.getAllProvider()); - } - - if (!provider) { - this._logService.trace('[IE] NO provider found'); + if (!agent) { + this._logService.trace('[IE] NO agent found'); return undefined; } + this._onWillStartSession.fire(editor); const textModel = editor.getModel(); const selection = editor.getSelection(); - let rawSession: IInlineChatSession | undefined | null; - try { - rawSession = await raceCancellation( - Promise.resolve(provider.prepareInlineChatSession(textModel, selection, token)), - token - ); - } catch (error) { - this._logService.error('[IE] FAILED to prepare session', provider.extensionId); - this._logService.error(error); - throw new InlineChatError((error as Error)?.message || 'Failed to prepare session'); - } - if (!rawSession) { - this._logService.trace('[IE] NO session', provider.extensionId); - return undefined; - } + + const rawSession: IInlineChatSession = { + id: Math.random(), + wholeRange: new Range(selection.selectionStartLineNumber, selection.selectionStartColumn, selection.positionLineNumber, selection.positionColumn), + placeholder: agent.description, + slashCommands: agent.slashCommands.map(agentCommand => { + return { + command: agentCommand.name, + detail: agentCommand.description, + refer: agentCommand.name === 'explain' // TODO@jrieken @joyceerhl this should be cleaned up + } satisfies IInlineChatSlashCommand; + }) + }; + const store = new DisposableStore(); - this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.extensionId}`); + this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${agent.extensionId}`); const chatModel = this._chatService.startSession(ChatAgentLocation.Editor, token); if (!chatModel) { @@ -486,59 +193,52 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { lastResponseListener.clear(); // ONCE let inlineResponse: ErrorResponse | EmptyResponse | ReplyResponse; - if (response.agent?.id === _bridgeAgentId) { - // use result that was provided by - inlineResponse = this._lastResponsesFromBridgeAgent.get(response.requestId) ?? new ErrorResponse(new Error('Missing Response')); - this._lastResponsesFromBridgeAgent.delete(response.requestId); + // make an response from the ChatResponseModel + if (response.isCanceled) { + // error: cancelled + inlineResponse = new ErrorResponse(new CancellationError()); + } else if (response.result?.errorDetails) { + // error: "real" error + inlineResponse = new ErrorResponse(new Error(response.result.errorDetails.message)); + } else if (response.response.value.length === 0) { + // epmty response + inlineResponse = new EmptyResponse(); } else { - // make an artificial response from the ChatResponseModel - if (response.isCanceled) { - // error: cancelled - inlineResponse = new ErrorResponse(new CancellationError()); - } else if (response.result?.errorDetails) { - // error: "real" error - inlineResponse = new ErrorResponse(new Error(response.result.errorDetails.message)); - } else if (response.response.value.length === 0) { - // epmty response - inlineResponse = new EmptyResponse(); - } else { - // replay response - const markdownContent = new MarkdownString(); - const raw: IInlineChatBulkEditResponse = { - id: Math.random(), - type: InlineChatResponseType.BulkEdit, - message: markdownContent, - edits: { edits: [] }, - }; - for (const item of response.response.value) { - if (item.kind === 'markdownContent') { - markdownContent.value += item.content.value; - } else if (item.kind === 'textEditGroup') { - for (const group of item.edits) { - for (const edit of group) { - raw.edits.edits.push({ - resource: item.uri, - textEdit: edit, - versionId: undefined - }); - } + // replay response + const markdownContent = new MarkdownString(); + const raw: IInlineChatBulkEditResponse = { + id: Math.random(), + type: InlineChatResponseType.BulkEdit, + message: markdownContent, + edits: { edits: [] }, + }; + for (const item of response.response.value) { + if (item.kind === 'markdownContent') { + markdownContent.value += item.content.value; + } else if (item.kind === 'textEditGroup') { + for (const group of item.edits) { + for (const edit of group) { + raw.edits.edits.push({ + resource: item.uri, + textEdit: edit, + versionId: undefined + }); } } } - - inlineResponse = this._instaService.createInstance( - ReplyResponse, - raw, - markdownContent, - session.textModelN.uri, - modelAltVersionIdNow, - [], - e.request.id, - e.request.response - ); - } + + inlineResponse = this._instaService.createInstance( + ReplyResponse, + raw, + markdownContent, + session.textModelN.uri, + modelAltVersionIdNow, + [], + e.request.id, + e.request.response + ); } session.addExchange(new SessionExchange(session.lastInput!, inlineResponse)); @@ -551,38 +251,9 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { }); })); - store.add(this._chatService.onDidPerformUserAction(e => { - if (e.sessionId !== chatModel.sessionId) { - return; - } - - // TODO@jrieken VALIDATE candidate is proper, e.g check with `session.exchanges` - const request = chatModel.getRequests().find(request => request.id === e.requestId); - const candidate = request?.response?.result?.metadata?.inlineChatResponse; - - if (!candidate) { - return; - } - - let kind: InlineChatResponseFeedbackKind | undefined; - if (e.action.kind === 'vote') { - kind = e.action.direction === ChatAgentVoteDirection.Down ? InlineChatResponseFeedbackKind.Unhelpful : InlineChatResponseFeedbackKind.Helpful; - } else if (e.action.kind === 'bug') { - kind = InlineChatResponseFeedbackKind.Bug; - } else if (e.action.kind === 'inlineChat') { - kind = e.action.action === 'accepted' ? InlineChatResponseFeedbackKind.Accepted : InlineChatResponseFeedbackKind.Undone; - } - - if (!kind) { - return; - } - - provider.handleInlineChatResponseFeedback?.(rawSession, candidate, kind); - })); - - store.add(this._inlineChatService.onDidChangeProviders(e => { - if (e.removed === provider) { - this._logService.trace(`[IE] provider GONE for ${editor.getId()}, ${provider.extensionId}`); + store.add(this._chatAgentService.onDidChangeAgents(e => { + if (e === undefined && !this._chatAgentService.getAgent(agent.id)) { + this._logService.trace(`[IE] provider GONE for ${editor.getId()}, ${agent.extensionId}`); this._releaseSession(session, true); } })); @@ -625,7 +296,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { targetUri, textModel0, textModelN, - provider, rawSession, + agent, + rawSession, store.add(new SessionWholeRange(textModelN, wholeRange)), store.add(new HunkData(this._editorWorkerService, textModel0, textModelN)), chatModel @@ -659,7 +331,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { found = true; this._sessions.delete(oldKey); this._sessions.set(newKey, { ...data, editor: target }); - this._logService.trace(`[IE] did MOVE session for ${data.editor.getId()} to NEW EDITOR ${target.getId()}, ${session.provider.extensionId}`); + this._logService.trace(`[IE] did MOVE session for ${data.editor.getId()} to NEW EDITOR ${target.getId()}, ${session.agent.extensionId}`); this._onDidMoveSession.fire({ session, editor: target }); break; } @@ -696,7 +368,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const [key, value] = tuple; this._sessions.delete(key); - this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.provider.extensionId}`); + this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.agent.extensionId}`); this._onDidEndSession.fire({ editor: value.editor, session, endedByExternalCause: byServer }); value.store.dispose(); @@ -706,7 +378,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._keepRecording(session); const result = this._instaService.createInstance(StashedSession, editor, session, undoCancelEdits); this._onDidStashSession.fire({ editor, session }); - this._logService.trace(`[IE] did STASH session for ${editor.getId()}, ${session.provider.extensionId}`); + this._logService.trace(`[IE] did STASH session for ${editor.getId()}, ${session.agent.extensionId}`); return result; } @@ -751,89 +423,24 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } } -export class AgentInlineChatProvider implements IInlineChatSessionProvider { +export class InlineChatEnabler { - readonly extensionId: ExtensionIdentifier; - readonly label: string; - readonly supportIssueReporting?: boolean | undefined; + static Id = 'inlineChat.enabler'; + + private readonly _ctxHasProvider: IContextKey; constructor( - readonly agent: IChatAgent, - @IChatAgentService private readonly _chatAgentService: IChatAgentService, + @IContextKeyService contextKeyService: IContextKeyService, + @IChatAgentService chatAgentService: IChatAgentService ) { - this.label = agent.fullName ?? agent.name; - this.extensionId = agent.extensionId; - this.supportIssueReporting = agent.metadata.supportIssueReporting; + this._ctxHasProvider = CTX_INLINE_CHAT_HAS_PROVIDER.bindTo(contextKeyService); + chatAgentService.onDidChangeAgents(() => { + const hasEditorAgent = Boolean(chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)); + this._ctxHasProvider.set(hasEditorAgent); + }); } - async prepareInlineChatSession(model: ITextModel, range: ISelection, token: CancellationToken): Promise { - - // TODO@jrieken have a good welcome message - // const welcomeMessage = await this.agent.provideWelcomeMessage?.(ChatAgentLocation.Editor, token); - // const message = welcomeMessage?.filter(candidate => typeof candidate === 'string').join(''), - - return { - id: Math.random(), - wholeRange: new Range(range.selectionStartLineNumber, range.selectionStartColumn, range.positionLineNumber, range.positionColumn), - placeholder: this.agent.description, - slashCommands: this.agent.slashCommands.map(agentCommand => { - return { - command: agentCommand.name, - detail: agentCommand.description, - refer: agentCommand.name === 'explain' // TODO@jrieken @joyceerhl this should be cleaned up - } satisfies IInlineChatSlashCommand; - }) - }; + dispose() { + this._ctxHasProvider.reset(); } - - async provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): Promise { - - const workspaceEdit: WorkspaceEdit = { edits: [] }; - - await this._chatAgentService.invokeAgent(this.agent.id, { - sessionId: String(item.id), - requestId: request.requestId, - agentId: this.agent.id, - message: request.prompt, - location: ChatAgentLocation.Editor, - variables: { - variables: [{ - id: InlineChatContext.variableName, - name: InlineChatContext.variableName, - value: JSON.stringify(new InlineChatContext(request.previewDocument, request.selection, request.wholeRange)) - }] - } - }, part => { - - if (part.kind === 'markdownContent') { - progress.report({ markdownFragment: part.content.value }); - } else if (part.kind === 'agentDetection') { - progress.report({ slashCommand: part.command?.name }); - } else if (part.kind === 'textEdit') { - - if (isEqual(request.previewDocument, part.uri)) { - progress.report({ edits: part.edits }); - } else { - for (const textEdit of part.edits) { - workspaceEdit.edits.push({ resource: part.uri, textEdit, versionId: undefined }); - } - } - } - - }, [], token); - - return { - type: InlineChatResponseType.BulkEdit, - id: Math.random(), - edits: workspaceEdit - }; - } - - // handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void { - // throw new Error('Method not implemented.'); - // } - - // provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult { - // throw new Error('Method not implemented.'); - // } } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index fc2da4a247d..59bb7cafe09 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -3,20 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; -import { ITextModel } from 'vs/editor/common/model'; +import { TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProgress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; @@ -115,33 +112,46 @@ export interface IInlineChatCommandFollowup { export type IInlineChatFollowup = IInlineChatReplyFollowup | IInlineChatCommandFollowup; +/** + * @deprecated + */ export interface IInlineChatSessionProvider { extensionId: ExtensionIdentifier; label: string; - supportIssueReporting?: boolean; - prepareInlineChatSession(model: ITextModel, range: ISelection, token: CancellationToken): ProviderResult; - - provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): ProviderResult; - - provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; - - handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void; } +/** + * @deprecated + */ export const IInlineChatService = createDecorator('IInlineChatService'); +/** + * @deprecated + */ export interface InlineChatProviderChangeEvent { readonly added?: IInlineChatSessionProvider; readonly removed?: IInlineChatSessionProvider; } +/** + * @deprecated + */ export interface IInlineChatService { _serviceBrand: undefined; + /** + * @deprecated + */ onDidChangeProviders: Event; + /** + * @deprecated + */ addProvider(provider: IInlineChatSessionProvider): IDisposable; + /** + * @deprecated + */ getAllProvider(): Iterable; } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 8c2b1e99ff8..9570097d78f 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -30,7 +30,7 @@ import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browse import { HunkState, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; -import { EditMode, IInlineChatService, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { CancellationToken } from 'vs/base/common/cancellation'; import { assertType } from 'vs/base/common/types'; @@ -64,7 +64,6 @@ suite('InlineChatSession', function () { let editor: IActiveCodeEditor; let model: ITextModel; let instaService: TestInstantiationService; - let inlineChatService: InlineChatServiceImpl; let inlineChatSessionService: IInlineChatSessionService; @@ -124,8 +123,6 @@ suite('InlineChatSession', function () { instaService = store.add(workbenchInstantiationService(undefined, store).createChild(serviceCollection)); - - inlineChatService = instaService.get(IInlineChatService) as InlineChatServiceImpl; inlineChatSessionService = store.add(instaService.get(IInlineChatSessionService)); instaService.get(IChatAgentService).registerDynamicAgent({ @@ -136,7 +133,7 @@ suite('InlineChatSession', function () { id: 'testAgent', name: 'testAgent', isDefault: true, - locations: [ChatAgentLocation.Panel], + locations: [ChatAgentLocation.Editor], metadata: {}, slashCommands: [] }, { @@ -145,25 +142,7 @@ suite('InlineChatSession', function () { } }); - store.add(inlineChatService.addProvider({ - extensionId: nullExtensionDescription.identifier, - label: 'Unit Test', - prepareInlineChatSession() { - return { - id: Math.random() - }; - }, - provideResponse(session, request) { - return { - type: InlineChatResponseType.EditorEdit, - id: Math.random(), - edits: [{ - range: new Range(1, 1, 1, 1), - text: request.prompt - }] - }; - } - })); + model = store.add(instaService.get(IModelService).createModel('one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven', null)); editor = store.add(instantiateTestCodeEditor(instaService, model)); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts index a04c894f931..251bee99b88 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts @@ -7,15 +7,14 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { IMarkerData, IMarkerService } from 'vs/platform/markers/common/markers'; import { IRange } from 'vs/editor/common/core/range'; import { ICellExecutionError, ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { Iterable } from 'vs/base/common/iterator'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; +import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; type CellDiagnostic = { cellUri: URI; @@ -35,14 +34,14 @@ export class CellDiagnostics extends Disposable implements INotebookEditorContri private readonly notebookEditor: INotebookEditor, @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, @IMarkerService private readonly markerService: IMarkerService, - @IInlineChatService private readonly inlineChatService: IInlineChatService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); this.updateEnabled(); - this._register(inlineChatService.onDidChangeProviders(() => this.updateEnabled())); + this._register(chatAgentService.onDidChangeAgents(() => this.updateEnabled())); this._register(configurationService.onDidChangeConfiguration((e) => { if (e.affectsConfiguration(NotebookSetting.cellFailureDiagnostics)) { this.updateEnabled(); @@ -52,10 +51,10 @@ export class CellDiagnostics extends Disposable implements INotebookEditorContri private updateEnabled() { const settingEnabled = this.configurationService.getValue(NotebookSetting.cellFailureDiagnostics); - if (this.enabled && (!settingEnabled || Iterable.isEmpty(this.inlineChatService.getAllProvider()))) { + if (this.enabled && (!settingEnabled || !this.chatAgentService.getDefaultAgent(ChatAgentLocation.Editor))) { this.enabled = false; this.clearAll(); - } else if (!this.enabled && settingEnabled && !Iterable.isEmpty(this.inlineChatService.getAllProvider())) { + } else if (!this.enabled && settingEnabled && !!this.chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)) { this.enabled = true; if (!this.listening) { this.listening = true; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts index 2ee5da0b02b..eeef10e64d2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts @@ -12,9 +12,9 @@ import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { EmptyTextEditorHintContribution, IEmptyTextEditorHintOptions } from 'vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -30,7 +30,7 @@ export class EmptyCellEditorHintContribution extends EmptyTextEditorHintContribu @IHoverService hoverService: IHoverService, @IKeybindingService keybindingService: IKeybindingService, @IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService, - @IInlineChatService inlineChatService: IInlineChatService, + @IChatAgentService chatAgentService: IChatAgentService, @ITelemetryService telemetryService: ITelemetryService, @IProductService productService: IProductService ) { @@ -42,7 +42,7 @@ export class EmptyCellEditorHintContribution extends EmptyTextEditorHintContribu hoverService, keybindingService, inlineChatSessionService, - inlineChatService, + chatAgentService, telemetryService, productService ); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts b/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts index 3cd6c48a21e..c92241f6741 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts @@ -10,13 +10,10 @@ import { workbenchInstantiationService } from 'vs/workbench/test/browser/workben import { NullLogService } from 'vs/platform/log/common/log'; import { InitialHintAddon } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminal.initialHint.contribution'; import { getActiveDocument } from 'vs/base/browser/dom'; -import { IInlineChatSession, IInlineChatSessionProvider, InlineChatProviderChangeEvent } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatSessionProvider, InlineChatProviderChangeEvent } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Emitter } from 'vs/base/common/event'; import { strictEqual } from 'assert'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ITextModel } from 'vs/editor/common/model'; -import { ISelection } from 'vs/editor/common/core/selection'; -import { CancellationToken } from 'vs/base/common/cancellation'; // Test TerminalInitialHintAddon @@ -51,13 +48,7 @@ suite('Terminal Initial Hint Addon', () => { eventCount = 0; const provider: IInlineChatSessionProvider = { extensionId: new ExtensionIdentifier('test'), - label: 'blahblah', - prepareInlineChatSession(model: ITextModel, range: ISelection, token: CancellationToken): Promise { - throw new Error('Method not implemented.'); - }, - provideResponse() { - throw new Error('Method not implemented.'); - } + label: 'blahblah' }; _onDidChangeProviders.fire({ added: provider }); xterm.focus(); @@ -68,13 +59,7 @@ suite('Terminal Initial Hint Addon', () => { test('hint is not shown when there has been input', () => { const provider: IInlineChatSessionProvider = { extensionId: new ExtensionIdentifier('test'), - label: 'blahblah', - prepareInlineChatSession(model: ITextModel, range: ISelection, token: CancellationToken): Promise { - throw new Error('Method not implemented.'); - }, - provideResponse() { - throw new Error('Method not implemented.'); - } + label: 'blahblah' }; _onDidChangeProviders.fire({ added: provider }); xterm.writeln('data'); @@ -85,5 +70,3 @@ suite('Terminal Initial Hint Addon', () => { }); }); }); - -