Skip to content

Commit 3ebe5a4

Browse files
authored
prepare for not having a "real" inline chat provider (microsoft#210069)
* prepare for not having a "real" inline chat provider - add location to provideWelcomeMessage - add dynamic/fake inline chat provider for editor agents - fix inline content widget when not having a message * fix tests
1 parent d91184b commit 3ebe5a4

File tree

12 files changed

+185
-38
lines changed

12 files changed

+185
-38
lines changed

src/vs/workbench/api/browser/mainThreadChatAgents2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
120120

121121
return this._proxy.$provideFollowups(request, handle, result, { history }, token);
122122
},
123-
provideWelcomeMessage: (token: CancellationToken) => {
124-
return this._proxy.$provideWelcomeMessage(handle, token);
123+
provideWelcomeMessage: (location: ChatAgentLocation, token: CancellationToken) => {
124+
return this._proxy.$provideWelcomeMessage(handle, location, token);
125125
},
126126
provideSampleQuestions: (location: ChatAgentLocation, token: CancellationToken) => {
127127
return this._proxy.$provideSampleQuestions(handle, location, token);

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,7 @@ export interface ExtHostChatAgentsShape2 {
12461246
$acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void;
12471247
$acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void;
12481248
$invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise<IChatAgentCompletionItem[]>;
1249-
$provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>;
1249+
$provideWelcomeMessage(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>;
12501250
$provideSampleQuestions(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise<IChatFollowup[] | undefined>;
12511251
$releaseSession(sessionId: string): void;
12521252
}

src/vs/workbench/api/common/extHostChatAgents2.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,13 +375,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
375375
return items.map(typeConvert.ChatAgentCompletionItem.from);
376376
}
377377

378-
async $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> {
378+
async $provideWelcomeMessage(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> {
379379
const agent = this._agents.get(handle);
380380
if (!agent) {
381381
return;
382382
}
383383

384-
return await agent.provideWelcomeMessage(token);
384+
return await agent.provideWelcomeMessage(typeConvert.ChatLocation.to(location), token);
385385
}
386386

387387
async $provideSampleQuestions(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise<IChatFollowup[] | undefined> {
@@ -453,11 +453,11 @@ class ExtHostChatAgent {
453453
.filter(f => !(f && 'message' in f));
454454
}
455455

456-
async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> {
456+
async provideWelcomeMessage(location: vscode.ChatLocation, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> {
457457
if (!this._welcomeMessageProvider) {
458458
return [];
459459
}
460-
const content = await this._welcomeMessageProvider.provideWelcomeMessage(token);
460+
const content = await this._welcomeMessageProvider.provideWelcomeMessage(location, token);
461461
if (!content) {
462462
return [];
463463
}

src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
194194
}
195195

196196
const store = new DisposableStore();
197-
if (providerDescriptor.isDefault) {
198-
store.add(this.registerDefaultParticipant(providerDescriptor));
197+
if (providerDescriptor.isDefault && (!providerDescriptor.locations || providerDescriptor.locations?.includes(ChatAgentLocation.Panel))) {
198+
store.add(this.registerDefaultParticipantView(providerDescriptor));
199199
}
200200

201201
store.add(this._chatAgentService.registerAgent(
@@ -218,7 +218,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
218218
} satisfies IChatAgentData));
219219

220220
this._participantRegistrationDisposables.set(
221-
getParticipantKey(extension.description.identifier, providerDescriptor.name),
221+
getParticipantKey(extension.description.identifier, providerDescriptor.id),
222222
store
223223
);
224224
}
@@ -250,7 +250,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
250250
return viewContainer;
251251
}
252252

253-
private registerDefaultParticipant(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable {
253+
private registerDefaultParticipantView(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable {
254254
// Register View
255255
const viewDescriptor: IViewDescriptor[] = [{
256256
id: CHAT_VIEW_ID,

src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class InputEditorDecorations extends Disposable {
138138
},
139139
renderOptions: {
140140
after: {
141-
contentText: viewModel.inputPlaceholder ?? defaultAgent?.description ?? '',
141+
contentText: viewModel.inputPlaceholder || (defaultAgent?.description ?? ''),
142142
color: this.getPlaceholderColor()
143143
}
144144
}

src/vs/workbench/contrib/chat/common/chatAgents.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export interface IChatAgentData {
6363
export interface IChatAgentImplementation {
6464
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
6565
provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]>;
66-
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>;
66+
provideWelcomeMessage?(location: ChatAgentLocation, token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>;
6767
provideSampleQuestions?(location: ChatAgentLocation, token: CancellationToken): ProviderResult<IChatFollowup[] | undefined>;
6868
}
6969

@@ -335,9 +335,9 @@ export class MergedChatAgent implements IChatAgent {
335335
return [];
336336
}
337337

338-
provideWelcomeMessage(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> {
338+
provideWelcomeMessage(location: ChatAgentLocation, token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> {
339339
if (this.impl.provideWelcomeMessage) {
340-
return this.impl.provideWelcomeMessage(token);
340+
return this.impl.provideWelcomeMessage(location, token);
341341
}
342342

343343
return undefined;

src/vs/workbench/contrib/chat/common/chatServiceImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ export class ChatService extends Disposable implements IChatService {
350350
// Should have been registered during activation above!
351351
throw new ErrorNoTelemetry('No default agent registered');
352352
}
353-
const welcomeMessage = model.welcomeMessage ? undefined : await defaultAgent.provideWelcomeMessage?.(token) ?? undefined;
353+
const welcomeMessage = model.welcomeMessage ? undefined : await defaultAgent.provideWelcomeMessage?.(model.initialLocation, token) ?? undefined;
354354
const welcomeModel = welcomeMessage && this.instantiationService.createInstance(
355355
ChatWelcomeMessageModel,
356356
welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item),

src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ suite('VoiceChat', () => {
3434
this.name = id;
3535
}
3636
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> { throw new Error('Method not implemented.'); }
37-
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); }
37+
provideWelcomeMessage?(location: ChatAgentLocation, token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); }
3838
metadata = {};
3939
}
4040

src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ export class InlineChatContentWidget implements IContentWidget {
132132
beforeRender(): IDimension | null {
133133

134134
const maxHeight = this._widget.input.inputEditor.getOption(EditorOption.lineHeight) * 5;
135-
const inputEditorHeight = this._widget.inputEditor.getContentHeight();
135+
const inputEditorHeight = this._widget.contentHeight;
136136

137-
this._widget.inputEditor.layout(new dom.Dimension(360, Math.min(maxHeight, inputEditorHeight)));
137+
this._widget.layout(Math.min(maxHeight, inputEditorHeight), 360);
138138

139139
// const actualHeight = this._widget.inputPartHeight;
140140
// return new dom.Dimension(width, actualHeight);
@@ -194,9 +194,11 @@ export class InlineChatContentWidget implements IContentWidget {
194194
}
195195

196196
private _updateMessage(message: string) {
197-
this._messageContainer.classList.toggle('hidden', !message);
198-
const renderedMessage = renderLabelWithIcons(message);
199-
dom.reset(this._messageContainer, ...renderedMessage);
197+
if (message) {
198+
const renderedMessage = renderLabelWithIcons(message);
199+
dom.reset(this._messageContainer, ...renderedMessage);
200+
}
201+
this._messageContainer.style.display = message ? 'inherit' : 'none';
200202
this._editor.layoutContentWidget(this);
201203
}
202204
}

src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts

Lines changed: 159 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,41 @@
55
import { coalesceInPlace, isNonEmptyArray } from 'vs/base/common/arrays';
66
import { raceCancellation } from 'vs/base/common/async';
77
import { CancellationToken } from 'vs/base/common/cancellation';
8-
import { Codicon } from 'vs/base/common/codicons';
98
import { CancellationError } from 'vs/base/common/errors';
109
import { Emitter, Event } from 'vs/base/common/event';
1110
import { MarkdownString } from 'vs/base/common/htmlContent';
1211
import { Iterable } from 'vs/base/common/iterator';
13-
import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
12+
import { DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
1413
import { LRUCache } from 'vs/base/common/map';
1514
import { Schemas } from 'vs/base/common/network';
1615
import { URI } from 'vs/base/common/uri';
1716
import { generateUuid } from 'vs/base/common/uuid';
1817
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
19-
import { Range } from 'vs/editor/common/core/range';
20-
import { TextEdit } from 'vs/editor/common/languages';
18+
import { IRange, Range } from 'vs/editor/common/core/range';
19+
import { TextEdit, WorkspaceEdit } from 'vs/editor/common/languages';
2120
import { ITextModel, IValidEditOperation } from 'vs/editor/common/model';
2221
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
2322
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
2423
import { IModelService } from 'vs/editor/common/services/model';
2524
import { ITextModelService } from 'vs/editor/common/services/resolverService';
2625
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2726
import { ILogService } from 'vs/platform/log/common/log';
28-
import { Progress } from 'vs/platform/progress/common/progress';
27+
import { IProgress, Progress } from 'vs/platform/progress/common/progress';
2928
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
3029
import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
31-
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
30+
import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
3231
import { IChatFollowup, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
33-
import { EditMode, IInlineChatBulkEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, IInlineChatService, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
32+
import { EditMode, IInlineChatBulkEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, IInlineChatService, IInlineChatSession, IInlineChatSessionProvider, IInlineChatSlashCommand, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
3433
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
35-
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
3634
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
3735
import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession';
3836
import { IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer, Recording } from './inlineChatSessionService';
3937
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
38+
import { ISelection } from 'vs/editor/common/core/selection';
39+
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
40+
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
41+
import { Codicon } from 'vs/base/common/codicons';
42+
import { isEqual } from 'vs/base/common/resources';
4043

4144
class BridgeAgent implements IChatAgentImplementation {
4245

@@ -186,7 +189,7 @@ class BridgeAgent implements IChatAgentImplementation {
186189
return chatFollowups;
187190
}
188191

189-
provideWelcomeMessage(token: CancellationToken): string[] {
192+
provideWelcomeMessage(location: ChatAgentLocation, token: CancellationToken): string[] {
190193
// without this provideSampleQuestions is not called
191194
return [];
192195
}
@@ -227,6 +230,17 @@ export class InlineChatError extends Error {
227230
const _bridgeAgentId = 'brigde.editor';
228231
const _inlineChatContext = '_inlineChatContext';
229232

233+
class InlineChatContext {
234+
235+
static readonly variableName = '_inlineChatContext';
236+
237+
constructor(
238+
readonly uri: URI,
239+
readonly selection: ISelection,
240+
readonly wholeRange: IRange,
241+
) { }
242+
}
243+
230244
export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
231245

232246
declare _serviceBrand: undefined;
@@ -265,6 +279,37 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
265279
@IChatVariablesService chatVariableService: IChatVariablesService,
266280
) {
267281

282+
const fakeProviders = this._store.add(new DisposableMap<string, IDisposable>());
283+
284+
this._store.add(this._chatAgentService.onDidChangeAgents(() => {
285+
286+
const providersNow = new Set<string>();
287+
288+
for (const agent of this._chatAgentService.getActivatedAgents()) {
289+
if (agent.id === _bridgeAgentId) {
290+
// not interesting
291+
continue;
292+
}
293+
if (!agent.locations.includes(ChatAgentLocation.Editor) || !agent.isDefault) {
294+
// not interesting
295+
continue;
296+
}
297+
providersNow.add(agent.id);
298+
299+
if (!fakeProviders.has(agent.id)) {
300+
fakeProviders.set(agent.id, _inlineChatService.addProvider(_instaService.createInstance(AgentInlineChatProvider, agent)));
301+
this._logService.info(`ADDED inline chat provider for agent ${agent.id}`);
302+
}
303+
}
304+
305+
for (const [id] of fakeProviders) {
306+
if (!providersNow.has(id)) {
307+
fakeProviders.deleteAndDispose(id);
308+
this._logService.info(`REMOVED inline chat provider for agent ${id}`);
309+
}
310+
}
311+
}));
312+
268313
// MARK: register fake chat agent
269314
const addOrRemoveBridgeAgent = () => {
270315
const that = this;
@@ -339,10 +384,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
339384
if (data.session.chatModel === model) {
340385
return [{
341386
level: 'full',
342-
value: JSON.stringify({
343-
selection: data.editor.getSelection(),
344-
wholeRange: data.session.wholeRange.trackedInitialRange
345-
})
387+
value: JSON.stringify(new InlineChatContext(data.session.textModelN.uri, data.editor.getSelection()!, data.session.wholeRange.trackedInitialRange))
346388
}];
347389
}
348390
}
@@ -360,7 +402,21 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
360402

361403
async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise<Session | undefined> {
362404

363-
const provider = Iterable.first(this._inlineChatService.getAllProvider());
405+
const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Editor);
406+
let provider: IInlineChatSessionProvider | undefined;
407+
if (agent) {
408+
for (const candidate of this._inlineChatService.getAllProvider()) {
409+
if (candidate instanceof AgentInlineChatProvider && candidate.agent === agent) {
410+
provider = candidate;
411+
break;
412+
}
413+
}
414+
}
415+
416+
if (!provider) {
417+
provider = Iterable.first(this._inlineChatService.getAllProvider());
418+
}
419+
364420
if (!provider) {
365421
this._logService.trace('[IE] NO provider found');
366422
return undefined;
@@ -684,3 +740,92 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
684740
return this._recordings;
685741
}
686742
}
743+
744+
export class AgentInlineChatProvider implements IInlineChatSessionProvider {
745+
746+
readonly extensionId: ExtensionIdentifier;
747+
readonly label: string;
748+
readonly supportIssueReporting?: boolean | undefined;
749+
750+
constructor(
751+
readonly agent: IChatAgent,
752+
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
753+
) {
754+
this.label = agent.name;
755+
this.extensionId = agent.extensionId;
756+
this.supportIssueReporting = agent.metadata.supportIssueReporting;
757+
}
758+
759+
async prepareInlineChatSession(model: ITextModel, range: ISelection, token: CancellationToken): Promise<IInlineChatSession> {
760+
761+
// TODO@jrieken have a good welcome message
762+
// const welcomeMessage = await this.agent.provideWelcomeMessage?.(ChatAgentLocation.Editor, token);
763+
// const message = welcomeMessage?.filter(candidate => typeof candidate === 'string').join(''),
764+
765+
return {
766+
id: Math.random(),
767+
wholeRange: new Range(range.selectionStartLineNumber, range.selectionStartColumn, range.positionLineNumber, range.positionColumn),
768+
placeholder: this.agent.description,
769+
slashCommands: this.agent.slashCommands.map(agentCommand => {
770+
return {
771+
command: agentCommand.name,
772+
detail: agentCommand.description,
773+
refer: agentCommand.name === 'explain' // TODO@jrieken @joyceerhl this should be cleaned up
774+
} satisfies IInlineChatSlashCommand;
775+
})
776+
};
777+
}
778+
779+
async provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress<IInlineChatProgressItem>, token: CancellationToken): Promise<IInlineChatResponse> {
780+
781+
const workspaceEdit: WorkspaceEdit = { edits: [] };
782+
783+
await this._chatAgentService.invokeAgent(this.agent.id, {
784+
sessionId: String(item.id),
785+
requestId: request.requestId,
786+
agentId: this.agent.id,
787+
message: request.prompt,
788+
location: ChatAgentLocation.Editor,
789+
variables: {
790+
variables: [{
791+
name: InlineChatContext.variableName,
792+
values: [{
793+
level: 'full',
794+
value: JSON.stringify(new InlineChatContext(request.previewDocument, request.selection, request.wholeRange))
795+
}]
796+
}]
797+
}
798+
}, part => {
799+
800+
if (part.kind === 'markdownContent') {
801+
progress.report({ markdownFragment: part.content.value });
802+
} else if (part.kind === 'agentDetection') {
803+
progress.report({ slashCommand: part.command?.name });
804+
} else if (part.kind === 'textEdit') {
805+
806+
if (isEqual(request.previewDocument, part.uri)) {
807+
progress.report({ edits: part.edits });
808+
} else {
809+
for (const textEdit of part.edits) {
810+
workspaceEdit.edits.push({ resource: part.uri, textEdit, versionId: undefined });
811+
}
812+
}
813+
}
814+
815+
}, [], token);
816+
817+
return {
818+
type: InlineChatResponseType.BulkEdit,
819+
id: Math.random(),
820+
edits: workspaceEdit
821+
};
822+
}
823+
824+
// handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void {
825+
// throw new Error('Method not implemented.');
826+
// }
827+
828+
// provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult<IInlineChatFollowup[]> {
829+
// throw new Error('Method not implemented.');
830+
// }
831+
}

0 commit comments

Comments
 (0)