Skip to content

Commit 0b571a2

Browse files
authored
Change ChatAgent history to include real requests, response data, variables, etc. (microsoft#202541)
* Change ChatAgent history to include real requests, response data, variables, etc. For microsoft#199908 * Tweak * Update snapshots * Clean up * Update addCompleteRequest * Filter out other progress types * Fix chat slash command data in history * Don't include slashCommand in history request at all, since it's deprecated anyway * undefined
1 parent a42d475 commit 0b571a2

22 files changed

+226
-88
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
8686
invoke: async (request, progress, history, token) => {
8787
this._pendingProgress.set(request.requestId, progress);
8888
try {
89-
return await this._proxy.$invokeAgent(handle, request.sessionId, request.requestId, request, { history }, token) ?? {};
89+
return await this._proxy.$invokeAgent(handle, request, { history }, token) ?? {};
9090
} finally {
9191
this._pendingProgress.delete(request.requestId);
9292
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
5151
import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views';
5252
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
5353
import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
54+
import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
5455
import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider';
5556
import { IChatAsyncContent, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
5657
import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables';
@@ -1201,8 +1202,18 @@ export interface IChatAgentCompletionItem {
12011202
documentation?: string | IMarkdownString;
12021203
}
12031204

1205+
export type IChatContentProgressDto =
1206+
| Dto<Exclude<IChatProgressResponseContent, IChatAsyncContent>>
1207+
| IChatAsyncContentDto;
1208+
1209+
export type IChatAgentHistoryEntryDto = {
1210+
request: IChatAgentRequest;
1211+
response: ReadonlyArray<IChatContentProgressDto>;
1212+
result: IChatAgentResult;
1213+
};
1214+
12041215
export interface ExtHostChatAgentsShape2 {
1205-
$invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise<IChatAgentResult | undefined>;
1216+
$invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise<IChatAgentResult | undefined>;
12061217
$provideSlashCommands(handle: number, token: CancellationToken): Promise<IChatAgentCommand[]>;
12071218
$provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise<IChatFollowup[]>;
12081219
$acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void;

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

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { coalesce } from 'vs/base/common/arrays';
67
import { DeferredPromise, raceCancellation } from 'vs/base/common/async';
78
import { CancellationToken } from 'vs/base/common/cancellation';
89
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -14,12 +15,11 @@ import { localize } from 'vs/nls';
1415
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
1516
import { ILogService } from 'vs/platform/log/common/log';
1617
import { Progress } from 'vs/platform/progress/common/progress';
17-
import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol';
18+
import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol';
1819
import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider';
1920
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
2021
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
2122
import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
22-
import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider';
2323
import { IChatFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
2424
import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
2525
import type * as vscode from 'vscode';
@@ -51,10 +51,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
5151
return agent.apiAgent;
5252
}
5353

54-
async $invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise<IChatAgentResult | undefined> {
54+
async $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise<IChatAgentResult | undefined> {
5555
// Clear the previous result so that $acceptFeedback or $acceptAction during a request will be ignored.
5656
// We may want to support sending those during a request.
57-
this._previousResultMap.delete(sessionId);
57+
this._previousResultMap.delete(request.sessionId);
5858

5959
const agent = this._agents.get(handle);
6060
if (!agent) {
@@ -79,13 +79,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
7979
const stopWatch = StopWatch.create(false);
8080
let firstProgress: number | undefined;
8181
try {
82+
const convertedHistory = await this.prepareHistory(agent, request, context);
8283
const task = agent.invoke(
83-
{
84-
prompt: request.message,
85-
variables: typeConvert.ChatVariable.objectTo(request.variables),
86-
slashCommand
87-
},
88-
{ history: context.history.map(typeConvert.ChatMessage.to) },
84+
typeConvert.ChatAgentRequest.to(request, slashCommand),
85+
{ history: convertedHistory },
8986
new Progress<vscode.ChatAgentExtendedProgress>(progress => {
9087
throwIfDone();
9188

@@ -101,7 +98,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
10198
}
10299

103100
if ('placeholder' in progress && 'resolvedContent' in progress) {
104-
const resolvedContent = Promise.all([this._proxy.$handleProgressChunk(requestId, convertedProgress), progress.resolvedContent]);
101+
const resolvedContent = Promise.all([this._proxy.$handleProgressChunk(request.requestId, convertedProgress), progress.resolvedContent]);
105102
raceCancellation(resolvedContent, token).then(res => {
106103
if (!res) {
107104
return; /* Cancelled */
@@ -113,29 +110,29 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
113110
return;
114111
}
115112

116-
this._proxy.$handleProgressChunk(requestId, convertedContent, progressHandle ?? undefined);
113+
this._proxy.$handleProgressChunk(request.requestId, convertedContent, progressHandle ?? undefined);
117114
});
118115
} else {
119-
this._proxy.$handleProgressChunk(requestId, convertedProgress);
116+
this._proxy.$handleProgressChunk(request.requestId, convertedProgress);
120117
}
121118
}),
122119
token
123120
);
124121

125122
return await raceCancellation(Promise.resolve(task).then((result) => {
126123
if (result) {
127-
this._previousResultMap.set(sessionId, result);
128-
let sessionResults = this._resultsBySessionAndRequestId.get(sessionId);
124+
this._previousResultMap.set(request.sessionId, result);
125+
let sessionResults = this._resultsBySessionAndRequestId.get(request.sessionId);
129126
if (!sessionResults) {
130127
sessionResults = new Map();
131-
this._resultsBySessionAndRequestId.set(sessionId, sessionResults);
128+
this._resultsBySessionAndRequestId.set(request.sessionId, sessionResults);
132129
}
133-
sessionResults.set(requestId, result);
130+
sessionResults.set(request.requestId, result);
134131

135132
const timings = { firstProgress: firstProgress, totalElapsed: stopWatch.elapsed() };
136133
return { errorDetails: result.errorDetails, timings };
137134
} else {
138-
this._previousResultMap.delete(sessionId);
135+
this._previousResultMap.delete(request.sessionId);
139136
}
140137

141138
return undefined;
@@ -151,6 +148,19 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
151148
}
152149
}
153150

151+
private async prepareHistory<T extends vscode.ChatAgentResult2>(agent: ExtHostChatAgent<T>, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<vscode.ChatAgentHistoryEntry[]> {
152+
return coalesce(await Promise.all(context.history
153+
.map(async h => {
154+
const result = request.agentId === h.request.agentId && this._resultsBySessionAndRequestId.get(request.sessionId)?.get(h.request.requestId)
155+
|| h.result;
156+
return {
157+
request: typeConvert.ChatAgentRequest.to(h.request, undefined),
158+
response: coalesce(h.response.map(r => typeConvert.ChatResponseProgress.toProgressContent(r))),
159+
result
160+
} satisfies vscode.ChatAgentHistoryEntry;
161+
})));
162+
}
163+
154164
$releaseSession(sessionId: string): void {
155165
this._previousResultMap.delete(sessionId);
156166
this._resultsBySessionAndRequestId.delete(sessionId);
@@ -248,7 +258,7 @@ class ExtHostChatAgent<TResult extends vscode.ChatAgentResult2> {
248258

249259
constructor(
250260
public readonly extension: IExtensionDescription,
251-
private readonly _id: string,
261+
public readonly id: string,
252262
private readonly _proxy: MainThreadChatAgentsShape2,
253263
private readonly _handle: number,
254264
private readonly _callback: vscode.ChatAgentExtendedHandler,
@@ -352,7 +362,7 @@ class ExtHostChatAgent<TResult extends vscode.ChatAgentResult2> {
352362
const that = this;
353363
return {
354364
get name() {
355-
return that._id;
365+
return that.id;
356366
},
357367
get description() {
358368
return that._description ?? '';

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

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as htmlContent from 'vs/base/common/htmlContent';
1111
import { DisposableStore } from 'vs/base/common/lifecycle';
1212
import { ResourceMap, ResourceSet } from 'vs/base/common/map';
1313
import { marked } from 'vs/base/common/marked/marked';
14-
import { parse } from 'vs/base/common/marshalling';
14+
import { parse, revive } from 'vs/base/common/marshalling';
1515
import { Mimes } from 'vs/base/common/mime';
1616
import { cloneAndChange } from 'vs/base/common/objects';
1717
import { isEmptyObject, isNumber, isString, isUndefinedOrNull } from 'vs/base/common/types';
@@ -34,6 +34,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
3434
import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi';
3535
import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor';
3636
import { IViewBadge } from 'vs/workbench/common/views';
37+
import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents';
3738
import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider';
3839
import { IChatFollowup, IChatReplyFollowup, IChatResponseCommandFollowup } from 'vs/workbench/contrib/chat/common/chatService';
3940
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
@@ -46,6 +47,7 @@ import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedT
4647
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
4748
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
4849
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
50+
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
4951
import type * as vscode from 'vscode';
5052
import * as types from './extHostTypes';
5153

@@ -120,6 +122,12 @@ export namespace Range {
120122
}
121123
}
122124

125+
export namespace Location {
126+
export function to(location: Dto<languages.Location>): vscode.Location {
127+
return new types.Location(URI.revive(location.uri), Range.to(location.range));
128+
}
129+
}
130+
123131
export namespace TokenType {
124132
export function to(type: encodedTokenAttributes.StandardTokenType): types.StandardTokenType {
125133
switch (type) {
@@ -2368,6 +2376,69 @@ export namespace ChatResponseProgress {
23682376
return undefined;
23692377
}
23702378
}
2379+
2380+
export function to(progress: extHostProtocol.IChatProgressDto): vscode.ChatAgentProgress | undefined {
2381+
switch (progress.kind) {
2382+
case 'markdownContent':
2383+
case 'inlineReference':
2384+
case 'treeData':
2385+
return ChatResponseProgress.to(progress);
2386+
case 'content':
2387+
return { content: progress.content };
2388+
case 'usedContext':
2389+
return { documents: progress.documents.map(d => ({ uri: URI.revive(d.uri), version: d.version, ranges: d.ranges.map(r => Range.to(r)) })) };
2390+
case 'reference':
2391+
return {
2392+
reference:
2393+
isUriComponents(progress.reference) ?
2394+
URI.revive(progress.reference) :
2395+
Location.to(progress.reference)
2396+
};
2397+
case 'agentDetection':
2398+
// For simplicity, don't sent back the 'extended' types
2399+
return undefined;
2400+
case 'progressMessage':
2401+
return { message: progress.content.value };
2402+
case 'vulnerability':
2403+
return { content: progress.content, vulnerabilities: progress.vulnerabilities };
2404+
default:
2405+
// Unknown type, eg something in history that was removed? Ignore
2406+
return undefined;
2407+
}
2408+
}
2409+
2410+
export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto): vscode.ChatAgentContentProgress | undefined {
2411+
switch (progress.kind) {
2412+
case 'markdownContent':
2413+
// For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text
2414+
return { content: progress.content.value };
2415+
case 'inlineReference':
2416+
return {
2417+
inlineReference:
2418+
isUriComponents(progress.inlineReference) ?
2419+
URI.revive(progress.inlineReference) :
2420+
Location.to(progress.inlineReference),
2421+
title: progress.name
2422+
};
2423+
case 'treeData':
2424+
return { treeData: revive(progress.treeData) };
2425+
default:
2426+
// Unknown type, eg something in history that was removed? Ignore
2427+
return undefined;
2428+
}
2429+
}
2430+
}
2431+
2432+
export namespace ChatAgentRequest {
2433+
export function to(request: IChatAgentRequest, slashCommand: vscode.ChatAgentSlashCommand | undefined): vscode.ChatAgentRequest {
2434+
return {
2435+
prompt: request.message,
2436+
variables: ChatVariable.objectTo(request.variables),
2437+
slashCommand,
2438+
subCommand: request.command,
2439+
agentId: request.agentId,
2440+
};
2441+
}
23712442
}
23722443

23732444
export namespace ChatAgentCompletionItem {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ class QuickChat extends Disposable {
292292
if (request.response?.response.value || request.response?.errorDetails) {
293293
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
294294
request.message as IParsedChatRequest,
295+
request.variableData,
295296
{
296297
message: request.response.response.value,
297298
errorDetails: request.response.errorDetails,

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { Iterable } from 'vs/base/common/iterator';
99
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
1010
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
1111
import { ChatDynamicVariableModel } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables';
12-
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
12+
import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel';
1313
import { IParsedChatRequest, ChatRequestVariablePart, ChatRequestDynamicVariablePart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
14-
import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IChatVariableResolveResult, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables';
14+
import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables';
1515

1616
interface IChatData {
1717
data: IChatVariableData;
@@ -28,7 +28,7 @@ export class ChatVariablesService implements IChatVariablesService {
2828
) {
2929
}
3030

31-
async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult> {
31+
async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise<IChatRequestVariableData> {
3232
const resolvedVariables: Record<string, IChatRequestVariableValue[]> = {};
3333
const jobs: Promise<any>[] = [];
3434

@@ -62,7 +62,7 @@ export class ChatVariablesService implements IChatVariablesService {
6262

6363
return {
6464
variables: resolvedVariables,
65-
prompt: parsedPrompt.join('').trim()
65+
message: parsedPrompt.join('').trim()
6666
};
6767
}
6868

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
539539
const input = !opts ? editorValue :
540540
'query' in opts ? opts.query :
541541
`${opts.prefix} ${editorValue}`;
542-
const isUserQuery = !opts || 'query' in opts;
542+
const isUserQuery = !opts || 'prefix' in opts;
543543
const result = await this.chatService.sendRequest(this.viewModel.sessionId, input);
544544

545545
if (result) {

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'
1111
import { ThemeIcon } from 'vs/base/common/themables';
1212
import { URI } from 'vs/base/common/uri';
1313
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
14-
import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider';
14+
import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
1515
import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService';
1616
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
1717

@@ -22,8 +22,14 @@ export interface IChatAgentData {
2222
metadata: IChatAgentMetadata;
2323
}
2424

25+
export interface IChatAgentHistoryEntry {
26+
request: IChatAgentRequest;
27+
response: ReadonlyArray<IChatProgressResponseContent>;
28+
result: IChatAgentResult;
29+
}
30+
2531
export interface IChatAgent extends IChatAgentData {
26-
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatMessage[], token: CancellationToken): Promise<IChatAgentResult>;
32+
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
2733
provideFollowups?(sessionId: string, token: CancellationToken): Promise<IChatFollowup[]>;
2834
provideSlashCommands(token: CancellationToken): Promise<IChatAgentCommand[]>;
2935
}
@@ -72,6 +78,7 @@ export interface IChatAgentMetadata {
7278
export interface IChatAgentRequest {
7379
sessionId: string;
7480
requestId: string;
81+
agentId: string;
7582
command?: string;
7683
message: string;
7784
variables: Record<string, IChatRequestVariableValue[]>;
@@ -93,7 +100,7 @@ export interface IChatAgentService {
93100
_serviceBrand: undefined;
94101
readonly onDidChangeAgents: Event<void>;
95102
registerAgent(agent: IChatAgent): IDisposable;
96-
invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatMessage[], token: CancellationToken): Promise<IChatAgentResult>;
103+
invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
97104
getFollowups(id: string, sessionId: string, token: CancellationToken): Promise<IChatFollowup[]>;
98105
getAgents(): Array<IChatAgent>;
99106
getAgent(id: string): IChatAgent | undefined;
@@ -163,7 +170,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
163170
return data?.agent;
164171
}
165172

166-
async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatMessage[], token: CancellationToken): Promise<IChatAgentResult> {
173+
async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
167174
const data = this._agents.get(id);
168175
if (!data) {
169176
throw new Error(`No agent with id ${id}`);

0 commit comments

Comments
 (0)