Skip to content

Commit 21a7b8b

Browse files
committed
add support for implicit requests in chat service and view model
1 parent 4401752 commit 21a7b8b

7 files changed

Lines changed: 41 additions & 7 deletions

File tree

src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
832832
templateData.rowContainer.classList.toggle('confirmation-message', isRequestVM(element) && !!element.confirmation);
833833

834834
// TODO: @justschen decide if we want to hide the header for requests or not
835-
const shouldShowHeader = isResponseVM(element) && !this.rendererOptions.noHeader;
835+
const shouldShowHeader = (isResponseVM(element) && !this.rendererOptions.noHeader) || false;
836836
templateData.header?.classList.toggle('header-disabled', !shouldShowHeader);
837837

838838
if (isRequestVM(element) && element.confirmation) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,14 @@ export interface IChatSendRequestOptions {
14071407
*/
14081408
pauseQueue?: boolean;
14091409

1410+
/**
1411+
* When true, the request is rendered as a compact tool-progress-style line
1412+
* instead of a full user message bubble. Used for system-initiated notifications
1413+
* such as terminal command completion.
1414+
*/
1415+
isImplicit?: boolean;
1416+
1417+
14101418
}
14111419

14121420
export type IChatModelReference = IReference<IChatModel>;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ export class ChatService extends Disposable implements IChatService {
861861
attachedContext: options.attachedContext,
862862
modelId: options.userSelectedModelId,
863863
userSelectedTools: options.userSelectedTools?.get(),
864+
isImplicit: options.isImplicit,
864865
});
865866

866867
const deferred = new DeferredPromise<ChatSendResult>();
@@ -1163,7 +1164,7 @@ export class ChatService extends Disposable implements IChatService {
11631164
if (agentPart || (defaultAgent && !commandPart)) {
11641165
const prepareChatAgentRequest = (agent: IChatAgentData, command?: IChatAgentCommand, enableCommandDetection?: boolean, chatRequest?: ChatRequestModel, isParticipantDetected?: boolean): IChatAgentRequest => {
11651166
const initVariableData: IChatRequestVariableData = { variables: [] };
1166-
request = chatRequest ?? model.addRequest(parsedRequest, initVariableData, attempt, options?.modeInfo, agent, command, options?.confirmation, options?.locationData, options?.attachedContext, undefined, options?.userSelectedModelId, options?.userSelectedTools?.get());
1167+
request = chatRequest ?? model.addRequest(parsedRequest, initVariableData, attempt, options?.modeInfo, agent, command, options?.confirmation, options?.locationData, options?.attachedContext, undefined, options?.userSelectedModelId, options?.userSelectedTools?.get(), undefined, options?.isImplicit);
11671168

11681169
let variableData: IChatRequestVariableData;
11691170
let message: string;

src/vs/workbench/contrib/chat/common/model/chatModel.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export interface IChatRequestModel {
126126
setShouldBeBlocked(value: boolean): void;
127127
readonly modelId?: string;
128128
readonly userSelectedTools?: UserSelectedTools;
129+
readonly isImplicit?: boolean;
129130
}
130131

131132
export interface ICodeBlockInfo {
@@ -342,6 +343,7 @@ export interface IChatRequestModelParameters {
342343
restoredId?: string;
343344
editedFileEvents?: IChatAgentEditedFileEvent[];
344345
userSelectedTools?: UserSelectedTools;
346+
isImplicit?: boolean;
345347
}
346348

347349
export class ChatRequestModel implements IChatRequestModel {
@@ -354,6 +356,7 @@ export class ChatRequestModel implements IChatRequestModel {
354356
public readonly modelId?: string;
355357
public readonly modeInfo?: IChatRequestModeInfo;
356358
public readonly userSelectedTools?: UserSelectedTools;
359+
public readonly isImplicit?: boolean;
357360

358361
private readonly _shouldBeBlocked = observableValue<boolean>(this, false);
359362
public get shouldBeBlocked(): IObservable<boolean> {
@@ -425,6 +428,7 @@ export class ChatRequestModel implements IChatRequestModel {
425428
this.id = params.restoredId ?? 'request_' + generateUuid();
426429
this._editedFileEvents = params.editedFileEvents;
427430
this.userSelectedTools = params.userSelectedTools;
431+
this.isImplicit = params.isImplicit;
428432
}
429433

430434
adoptTo(session: ChatModel) {
@@ -1510,6 +1514,7 @@ export interface ISerializableChatRequestData extends ISerializableChatResponseD
15101514
editedFileEvents?: IChatAgentEditedFileEvent[];
15111515
modelId?: string;
15121516
modeInfo?: IChatRequestModeInfo;
1517+
isImplicit?: boolean;
15131518
}
15141519

15151520
export interface ISerializableMarkdownInfo {
@@ -2375,6 +2380,7 @@ export class ChatModel extends Disposable implements IChatModel {
23752380
editedFileEvents: raw.editedFileEvents,
23762381
modelId: raw.modelId,
23772382
modeInfo: raw.modeInfo,
2383+
isImplicit: raw.isImplicit,
23782384
});
23792385
request.shouldBeRemovedOnSend = raw.isHidden ? { requestId: raw.requestId } : raw.shouldBeRemovedOnSend;
23802386
// eslint-disable-next-line @typescript-eslint/no-explicit-any, local/code-no-any-casts
@@ -2543,7 +2549,7 @@ export class ChatModel extends Disposable implements IChatModel {
25432549
this._onDidChange.fire({ kind: 'setHidden' });
25442550
}
25452551

2546-
addRequest(message: IParsedChatRequest, variableData: IChatRequestVariableData, attempt: number, modeInfo?: IChatRequestModeInfo, chatAgent?: IChatAgentData, slashCommand?: IChatAgentCommand, confirmation?: string, locationData?: IChatLocationData, attachments?: IChatRequestVariableEntry[], isCompleteAddedRequest?: boolean, modelId?: string, userSelectedTools?: UserSelectedTools, id?: string): ChatRequestModel {
2552+
addRequest(message: IParsedChatRequest, variableData: IChatRequestVariableData, attempt: number, modeInfo?: IChatRequestModeInfo, chatAgent?: IChatAgentData, slashCommand?: IChatAgentCommand, confirmation?: string, locationData?: IChatLocationData, attachments?: IChatRequestVariableEntry[], isCompleteAddedRequest?: boolean, modelId?: string, userSelectedTools?: UserSelectedTools, id?: string, isImplicit?: boolean): ChatRequestModel {
25472553
const editedFileEvents = [...this.currentEditedFileEvents.values()];
25482554
this.currentEditedFileEvents.clear();
25492555
const request = new ChatRequestModel({
@@ -2561,6 +2567,7 @@ export class ChatModel extends Disposable implements IChatModel {
25612567
modelId,
25622568
editedFileEvents: editedFileEvents.length ? editedFileEvents : undefined,
25632569
userSelectedTools,
2570+
isImplicit,
25642571
});
25652572
request.response = new ChatResponseModel({
25662573
responseContent: [],
@@ -2718,6 +2725,7 @@ export class ChatModel extends Disposable implements IChatModel {
27182725
editedFileEvents: r.editedFileEvents,
27192726
modelId: r.modelId,
27202727
modeInfo: r.modeInfo,
2728+
isImplicit: r.isImplicit || undefined,
27212729
...r.response?.toJSON(),
27222730
};
27232731
}),

src/vs/workbench/contrib/chat/common/model/chatSessionOperationLog.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ const requestSchema = Adapt.object<IChatRequestModel, ISerializableChatRequestDa
151151
codeCitations: Adapt.v(m => m.response?.codeCitations, objectsEqual),
152152
timeSpentWaiting: Adapt.v(m => m.response?.timestamp), // based on response timestamp
153153
modeInfo: Adapt.v(m => m.modeInfo, objectsEqual),
154+
isImplicit: Adapt.v(m => m.isImplicit),
154155
}, {
155156
sealed: (o) => o.modelState?.value === ResponseModelState.Cancelled || o.modelState?.value === ResponseModelState.Failed || o.modelState?.value === ResponseModelState.Complete,
156157
});

src/vs/workbench/contrib/chat/common/model/chatViewModel.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export interface IChatRequestViewModel {
9898
readonly timestamp: number;
9999
/** The kind of pending request, or undefined if not pending */
100100
readonly pendingKind?: ChatRequestQueueKind;
101+
readonly isImplicit?: boolean;
101102
}
102103

103104
export interface IChatResponseMarkdownRenderData {
@@ -334,7 +335,16 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
334335
}
335336

336337
getItems(): (IChatRequestViewModel | IChatResponseViewModel | IChatPendingDividerViewModel)[] {
337-
let items: (IChatRequestViewModel | IChatResponseViewModel | IChatPendingDividerViewModel)[] = this._items.filter((item) => !item.shouldBeRemovedOnSend || item.shouldBeRemovedOnSend.afterUndoStop);
338+
let items: (IChatRequestViewModel | IChatResponseViewModel | IChatPendingDividerViewModel)[] = this._items.filter((item) => {
339+
if (item.shouldBeRemovedOnSend && !item.shouldBeRemovedOnSend.afterUndoStop) {
340+
return false;
341+
}
342+
// Hide implicit requests (system-initiated notifications like terminal completions)
343+
if (isRequestVM(item) && item.isImplicit) {
344+
return false;
345+
}
346+
return true;
347+
});
338348
if (this._options?.maxVisibleItems !== undefined && items.length > this._options.maxVisibleItems) {
339349
items = items.slice(-this._options.maxVisibleItems);
340350
}
@@ -345,10 +355,11 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
345355
const steeringRequests = pendingRequests.filter(p => p.kind === ChatRequestQueueKind.Steering);
346356
const queuedRequests = pendingRequests.filter(p => p.kind === ChatRequestQueueKind.Queued);
347357

348-
// Add steering requests with their divider first
349-
if (steeringRequests.length > 0) {
358+
// Add steering requests with their divider first (skip implicit ones)
359+
const visibleSteeringRequests = steeringRequests.filter(p => !p.request.isImplicit);
360+
if (visibleSteeringRequests.length > 0) {
350361
items.push({ kind: 'pendingDivider', id: 'pending-divider-steering', sessionResource: this._model.sessionResource, isComplete: true, dividerKind: ChatRequestQueueKind.Steering, currentRenderedHeight: undefined });
351-
for (const pending of steeringRequests) {
362+
for (const pending of visibleSteeringRequests) {
352363
const requestVM = this.instantiationService.createInstance(ChatRequestViewModel, pending.request, pending.kind);
353364
items.push(requestVM);
354365
}
@@ -477,6 +488,10 @@ export class ChatRequestViewModel implements IChatRequestViewModel {
477488
return this._pendingKind;
478489
}
479490

491+
get isImplicit() {
492+
return this._model.isImplicit;
493+
}
494+
480495
constructor(
481496
private readonly _model: IChatRequestModel,
482497
private readonly _pendingKind?: ChatRequestQueueKind,

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,6 +1807,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
18071807

18081808
this._chatService.sendRequest(chatSessionResource, message, {
18091809
queue: ChatRequestQueueKind.Steering,
1810+
isImplicit: true,
18101811
}).catch(e => {
18111812
this._logService.warn(`RunInTerminalTool: Failed to send completion notification for terminal ${termId}`, e);
18121813
});

0 commit comments

Comments
 (0)