57
return items.at(-1);
58
}
60
>
export function isChatTreeItem(item: unknown): item is IChatRequestViewModel | IChatResponseViewModel {
61
return isRequestVM(item) || isResponseVM(item);
62
}
64
>
export function assertIsResponseVM(item: unknown): asserts item is IChatResponseViewModel {
65
if (!isResponseVM(item)) {
66
throw new Error('Expected item to be IChatResponseViewModel');
67
}
68
}
70
>
export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | IChatSessionInitEvent | IChatSetHiddenEvent | null;
71
>
72
>
export interface IChatAddRequestEvent {
73
>
kind: 'addRequest';
74
>
}
75
>
76
>
export interface IChangePlaceholderEvent {
77
>
kind: 'changePlaceholder';
78
>
}
79
>
80
>
export interface IChatSessionInitEvent {
81
>
kind: 'initialize';
82
>
}
83
>
84
>
export interface IChatSetHiddenEvent {
85
>
kind: 'setHidden';
86
>
}
87
>
88
>
export interface IChatViewModel {
89
>
readonly model: IChatModel;
90
>
readonly sessionResource: URI;
91
>
readonly onDidDisposeModel: Event<void>;
92
>
readonly onDidChange: Event<IChatViewModelChangeEvent>;
93
>
readonly inputPlaceholder?: string;
94
>
getItems(): (IChatRequestViewModel | IChatResponseViewModel | IChatPendingDividerViewModel)[];
95
>
setInputPlaceholder(text: string): void;
96
>
resetInputPlaceholder(): void;
97
>
editing?: IChatRequestViewModel;
98
>
setEditing(editing: IChatRequestViewModel): void;
99
>
}
100
>
101
>
export interface IChatRequestViewModel {
102
>
readonly id: string;
103
>
readonly sessionResource: URI;
104
>
/** This ID updates every time the underlying data changes */
105
>
readonly dataId: string;
106
>
readonly username: string;
107
>
readonly avatarIcon?: URI | ThemeIcon;
108
>
readonly message: IParsedChatRequest | IChatFollowup;
109
>
readonly messageText: string;
110
>
readonly attempt: number;
111
>
readonly variables: readonly IChatRequestVariableEntry[];
112
>
currentRenderedHeight: number | undefined;
113
>
readonly contentReferences?: ReadonlyArray<IChatContentReference>;
114
>
readonly confirmation?: string;
115
>
readonly shouldBeRemovedOnSend: IChatRequestDisablement | undefined;
116
>
readonly isComplete: boolean;
117
>
readonly isCompleteAddedRequest: boolean;
118
>
readonly isTerminalCommand: boolean;
119
>
readonly slashCommand: IChatAgentCommand | undefined;
120
>
readonly agentOrSlashCommandDetected: boolean;
121
>
readonly shouldBeBlocked: IObservable<boolean>;
122
>
readonly attachedContext?: readonly IChatRequestVariableEntry[];
123
>
readonly modelId?: string;
124
>
readonly resolvedModelId?: string;
125
>
readonly timestamp: number;
126
>
readonly requestTimestamp: number | undefined;
127
>
/** The kind of pending request, or undefined if not pending */
128
>
readonly pendingKind?: ChatRequestQueueKind;
129
>
readonly isSystemInitiated?: boolean;
130
>
readonly systemInitiatedLabel?: string;
131
>
}
132
>
133
>
export interface IChatResponseMarkdownRenderData {
134
>
renderedWordCount: number;
135
>
lastRenderTime: number;
136
>
isFullyRendered: boolean;
137
>
originalMarkdown: IMarkdownString;
138
>
}
139
>
140
>
export interface IChatResponseMarkdownRenderData2 {
141
>
renderedWordCount: number;
142
>
lastRenderTime: number;
143
>
isFullyRendered: boolean;
144
>
originalMarkdown: IMarkdownString;
145
>
}
146
>
147
>
export interface IChatProgressMessageRenderData {
148
>
progressMessage: IChatProgressMessage;
149
>
150
>
/**
151
>
* Indicates whether this is part of a group of progress messages that are at the end of the response.
152
>
* (Not whether this particular item is the very last one in the response).
153
>
* Need to re-render and add to partsToRender when this changes.
154
>
*/
155
>
isAtEndOfResponse: boolean;
156
>
157
>
/**
158
>
* Whether this progress message the very last item in the response.
159
>
* Need to re-render to update spinner vs check when this changes.
160
>
*/
161
>
isLast: boolean;
162
>
}
163
>
164
>
export interface IChatTaskRenderData {
165
>
task: IChatTask;
166
>
isSettled: boolean;
167
>
progressLength: number;
168
>
}
169
>
170
>
export interface IChatResponseRenderData {
171
>
renderedParts: IChatRendererContent[];
172
>
173
>
renderedWordCount: number;
174
>
lastRenderTime: number;
175
>
}
176
>
177
>
/**
178
>
* Content type for references used during rendering, not in the model
179
>
*/
180
>
export interface IChatReferences {
181
>
references: ReadonlyArray<IChatContentReference>;
182
>
kind: 'references';
183
>
}
184
>
185
>
/**
186
>
* Content type for the "Working" progress message
187
>
*/
188
>
export interface IChatWorkingProgress {
189
>
kind: 'working';
190
>
content?: IMarkdownString;
191
>
}
192
>
193
>
194
>
/**
195
>
* Content type for citations used during rendering, not in the model
196
>
*/
197
>
export interface IChatCodeCitations {
198
>
citations: ReadonlyArray<IChatCodeCitation>;
199
>
kind: 'codeCitations';
200
>
}
201
>
202
>
export interface IChatErrorDetailsPart {
203
>
kind: 'errorDetails';
204
>
errorDetails: IChatResponseErrorDetails;
205
>
isLast: boolean;
206
>
}
207
>
208
>
export interface IChatChangesSummaryPart {
209
>
readonly kind: 'changesSummary';
210
>
readonly requestId: string;
211
>
readonly sessionResource: URI;
212
>
}
213
>
214
>
export interface IChatTurnPillsPart {
215
>
readonly kind: 'turnPills';
216
>
readonly requestId: string;
217
>
readonly sessionResource: URI;
218
>
}
219
>
220
>
/**
221
>
* Type for content parts rendered by IChatListRenderer (not necessarily in the model)
222
>
*/
223
>
export type IChatRendererContent = IChatProgressRenderableResponseContent | IChatReferences | IChatCodeCitations | IChatErrorDetailsPart | IChatChangesSummaryPart | IChatWorkingProgress | IChatMcpServersStarting | IChatMcpAuthenticationRequired | IChatMcpServersStartingSlow | IChatQuestionCarousel | IChatPlanReview | IChatDisabledClaudeHooksPart | IChatTurnPillsPart;
224
>
225
>
export interface IChatResponseViewModel {
226
>
readonly model: IChatResponseModel;
227
>
readonly id: string;
228
>
readonly session: IChatViewModel;
229
>
readonly sessionResource: URI;
230
>
/** This ID updates every time the underlying data changes */
231
>
readonly dataId: string;
232
>
/** The ID of the associated IChatRequestViewModel */
233
>
readonly requestId: string;
234
>
readonly username: string;
235
>
readonly agent?: IChatAgentData;
236
>
readonly slashCommand?: IChatAgentCommand;
237
>
readonly agentOrSlashCommandDetected: boolean;
238
>
readonly response: IResponse;
239
>
readonly usedContext: IChatUsedContext | undefined;
240
>
readonly contentReferences: ReadonlyArray<IChatContentReference>;
241
>
readonly codeCitations: ReadonlyArray<IChatCodeCitation>;
242
>
readonly progressMessages: ReadonlyArray<IChatProgressMessage>;
243
>
readonly isComplete: boolean;
244
>
readonly isCanceled: boolean;
245
>
readonly isStale: boolean;
246
>
readonly vote: ChatAgentVoteDirection | undefined;
247
>
readonly replyFollowups?: IChatFollowup[];
248
>
readonly errorDetails?: IChatResponseErrorDetails;
249
>
readonly result?: IChatAgentResult;
250
>
readonly contentUpdateTimings?: IChatStreamStats;
251
>
readonly confirmationAdjustedTimestamp: IObservable<number>;
252
>
readonly usageObs: IObservable<IChatUsage | undefined>;
253
>
readonly completionTokenCountObs: IObservable<number | undefined>;
254
>
readonly shouldBeRemovedOnSend: IChatRequestDisablement | undefined;
255
>
readonly isCompleteAddedRequest: boolean;
256
>
readonly isTerminalCommand: boolean;
257
>
renderData?: IChatResponseRenderData;
258
>
currentRenderedHeight: number | undefined;
259
>
setVote(vote: ChatAgentVoteDirection): void;
260
>
usedReferencesExpanded?: boolean;
261
>
vulnerabilitiesListExpanded: boolean;
262
>
setEditApplied(edit: IChatTextEditGroup, editCount: number): void;
263
>
readonly shouldBeBlocked: IObservable<boolean>;
264
>
}
265
>
266
>
export interface IChatPendingDividerViewModel {
267
>
readonly kind: 'pendingDivider';
268
>
readonly id: string; // e.g., 'pending-divider-steering' or 'pending-divider-queued'
269
>
readonly sessionResource: URI;
270
>
readonly isComplete: true;
271
>
readonly dividerKind: ChatRequestQueueKind;
272
>
readonly isSystemInitiated?: boolean;
273
>
currentRenderedHeight: number | undefined;
274
>
}
275
>
276
>
export interface IChatViewModelOptions {
277
>
/**
278
>
* Maximum number of items to return from getItems().
279
>
* When set, only the last N items are returned (most recent request/response pairs).
280
>
*/
281
>
readonly maxVisibleItems?: number;
282
>
}
283
>
284
>
export class ChatViewModel extends Disposable implements IChatViewModel {
285
>
286
>
private readonly _onDidDisposeModel = this._register(new Emitter<void>());
287
>
readonly onDidDisposeModel = this._onDidDisposeModel.event;
288
>
289
>
private readonly _onDidChange = this._register(new Emitter<IChatViewModelChangeEvent>());
290
>
readonly onDidChange = this._onDidChange.event;
291
>
292
>
private readonly _items: (ChatRequestViewModel | ChatResponseViewModel)[] = [];
293
>
294
>
private _inputPlaceholder: string | undefined = undefined;
295
>
get inputPlaceholder(): string | undefined {
296
>
return this._inputPlaceholder;
297
>
}
298
>
299
>
get model(): IChatModel {
300
return this._model;
301
}
303
>
setInputPlaceholder(text: string): void {
304
this._inputPlaceholder = text;
305
this._onDidChange.fire({ kind: 'changePlaceholder' });
306
}
308
>
resetInputPlaceholder(): void {
309
this._inputPlaceholder = undefined;
310
this._onDidChange.fire({ kind: 'changePlaceholder' });
311
}
313
>
get sessionResource(): URI {
314
return this._model.sessionResource;
315
}
317
>
constructor(
318
private readonly _model: IChatModel,
319
private readonly _options: IChatViewModelOptions | undefined,