mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
Merge pull request #287881 from mjbvz/dev/mjbvz/arbitrary-owl
Reapply timing and chat session changes
This commit is contained in:
@@ -899,6 +899,7 @@ export default tseslint.config(
|
||||
],
|
||||
'verbs': [
|
||||
'accept',
|
||||
'archive',
|
||||
'change',
|
||||
'close',
|
||||
'collapse',
|
||||
|
||||
@@ -382,7 +382,6 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
$onDidChangeChatSessionItems(handle: number): void {
|
||||
this._itemProvidersRegistrations.get(handle)?.onDidChangeItems.fire();
|
||||
}
|
||||
@@ -491,6 +490,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat
|
||||
resource: uri,
|
||||
iconPath: session.iconPath,
|
||||
tooltip: session.tooltip ? this._reviveTooltip(session.tooltip) : undefined,
|
||||
archived: session.archived,
|
||||
} satisfies IChatSessionItem;
|
||||
}));
|
||||
} catch (error) {
|
||||
|
||||
@@ -1530,6 +1530,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension, 'chatSessionsProvider');
|
||||
return extHostChatSessions.registerChatSessionItemProvider(extension, chatSessionType, provider);
|
||||
},
|
||||
createChatSessionItemController: (chatSessionType: string, refreshHandler: () => Thenable<void>) => {
|
||||
checkProposedApiEnabled(extension, 'chatSessionsProvider');
|
||||
return extHostChatSessions.createChatSessionItemController(extension, chatSessionType, refreshHandler);
|
||||
},
|
||||
registerChatSessionContentProvider(scheme: string, provider: vscode.ChatSessionContentProvider, chatParticipant: vscode.ChatParticipant, capabilities?: vscode.ChatSessionCapabilities) {
|
||||
checkProposedApiEnabled(extension, 'chatSessionsProvider');
|
||||
return extHostChatSessions.registerChatSessionContentProvider(extension, scheme, chatParticipant, provider, capabilities);
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
/* eslint-disable local/code-no-native-private */
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { coalesce } from '../../../base/common/arrays.js';
|
||||
import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
|
||||
import { CancellationError } from '../../../base/common/errors.js';
|
||||
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
|
||||
import { Emitter } from '../../../base/common/event.js';
|
||||
import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
|
||||
import { ResourceMap } from '../../../base/common/map.js';
|
||||
import { MarshalledId } from '../../../base/common/marshallingIds.js';
|
||||
import { URI, UriComponents } from '../../../base/common/uri.js';
|
||||
@@ -29,6 +31,177 @@ import { basename } from '../../../base/common/resources.js';
|
||||
import { Diagnostic } from './extHostTypeConverters.js';
|
||||
import { SymbolKind, SymbolKinds } from '../../../editor/common/languages.js';
|
||||
|
||||
type ChatSessionTiming = vscode.ChatSessionItem['timing'];
|
||||
|
||||
// #region Chat Session Item Controller
|
||||
|
||||
class ChatSessionItemImpl implements vscode.ChatSessionItem {
|
||||
#label: string;
|
||||
#iconPath?: vscode.IconPath;
|
||||
#description?: string | vscode.MarkdownString;
|
||||
#badge?: string | vscode.MarkdownString;
|
||||
#status?: vscode.ChatSessionStatus;
|
||||
#archived?: boolean;
|
||||
#tooltip?: string | vscode.MarkdownString;
|
||||
#timing?: ChatSessionTiming;
|
||||
#changes?: readonly vscode.ChatSessionChangedFile[] | { files: number; insertions: number; deletions: number };
|
||||
#onChanged: () => void;
|
||||
|
||||
readonly resource: vscode.Uri;
|
||||
|
||||
constructor(resource: vscode.Uri, label: string, onChanged: () => void) {
|
||||
this.resource = resource;
|
||||
this.#label = label;
|
||||
this.#onChanged = onChanged;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this.#label;
|
||||
}
|
||||
|
||||
set label(value: string) {
|
||||
if (this.#label !== value) {
|
||||
this.#label = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get iconPath(): vscode.IconPath | undefined {
|
||||
return this.#iconPath;
|
||||
}
|
||||
|
||||
set iconPath(value: vscode.IconPath | undefined) {
|
||||
if (this.#iconPath !== value) {
|
||||
this.#iconPath = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get description(): string | vscode.MarkdownString | undefined {
|
||||
return this.#description;
|
||||
}
|
||||
|
||||
set description(value: string | vscode.MarkdownString | undefined) {
|
||||
if (this.#description !== value) {
|
||||
this.#description = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get badge(): string | vscode.MarkdownString | undefined {
|
||||
return this.#badge;
|
||||
}
|
||||
|
||||
set badge(value: string | vscode.MarkdownString | undefined) {
|
||||
if (this.#badge !== value) {
|
||||
this.#badge = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get status(): vscode.ChatSessionStatus | undefined {
|
||||
return this.#status;
|
||||
}
|
||||
|
||||
set status(value: vscode.ChatSessionStatus | undefined) {
|
||||
if (this.#status !== value) {
|
||||
this.#status = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get archived(): boolean | undefined {
|
||||
return this.#archived;
|
||||
}
|
||||
|
||||
set archived(value: boolean | undefined) {
|
||||
if (this.#archived !== value) {
|
||||
this.#archived = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get tooltip(): string | vscode.MarkdownString | undefined {
|
||||
return this.#tooltip;
|
||||
}
|
||||
|
||||
set tooltip(value: string | vscode.MarkdownString | undefined) {
|
||||
if (this.#tooltip !== value) {
|
||||
this.#tooltip = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get timing(): ChatSessionTiming | undefined {
|
||||
return this.#timing;
|
||||
}
|
||||
|
||||
set timing(value: ChatSessionTiming | undefined) {
|
||||
if (this.#timing !== value) {
|
||||
this.#timing = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get changes(): readonly vscode.ChatSessionChangedFile[] | { files: number; insertions: number; deletions: number } | undefined {
|
||||
return this.#changes;
|
||||
}
|
||||
|
||||
set changes(value: readonly vscode.ChatSessionChangedFile[] | { files: number; insertions: number; deletions: number } | undefined) {
|
||||
if (this.#changes !== value) {
|
||||
this.#changes = value;
|
||||
this.#onChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChatSessionItemCollectionImpl implements vscode.ChatSessionItemCollection {
|
||||
readonly #items = new ResourceMap<vscode.ChatSessionItem>();
|
||||
#onItemsChanged: () => void;
|
||||
|
||||
constructor(onItemsChanged: () => void) {
|
||||
this.#onItemsChanged = onItemsChanged;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#items.size;
|
||||
}
|
||||
|
||||
replace(items: readonly vscode.ChatSessionItem[]): void {
|
||||
this.#items.clear();
|
||||
for (const item of items) {
|
||||
this.#items.set(item.resource, item);
|
||||
}
|
||||
this.#onItemsChanged();
|
||||
}
|
||||
|
||||
forEach(callback: (item: vscode.ChatSessionItem, collection: vscode.ChatSessionItemCollection) => unknown, thisArg?: any): void {
|
||||
for (const [_, item] of this.#items) {
|
||||
callback.call(thisArg, item, this);
|
||||
}
|
||||
}
|
||||
|
||||
add(item: vscode.ChatSessionItem): void {
|
||||
this.#items.set(item.resource, item);
|
||||
this.#onItemsChanged();
|
||||
}
|
||||
|
||||
delete(resource: vscode.Uri): void {
|
||||
this.#items.delete(resource);
|
||||
this.#onItemsChanged();
|
||||
}
|
||||
|
||||
get(resource: vscode.Uri): vscode.ChatSessionItem | undefined {
|
||||
return this.#items.get(resource);
|
||||
}
|
||||
|
||||
[Symbol.iterator](): Iterator<readonly [id: URI, chatSessionItem: vscode.ChatSessionItem]> {
|
||||
return this.#items.entries();
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
class ExtHostChatSession {
|
||||
private _stream: ChatAgentResponseStream;
|
||||
|
||||
@@ -62,13 +235,20 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
readonly extension: IExtensionDescription;
|
||||
readonly disposable: DisposableStore;
|
||||
}>();
|
||||
private readonly _chatSessionItemControllers = new Map<number, {
|
||||
readonly sessionType: string;
|
||||
readonly controller: vscode.ChatSessionItemController;
|
||||
readonly extension: IExtensionDescription;
|
||||
readonly disposable: DisposableStore;
|
||||
}>();
|
||||
private _nextChatSessionItemProviderHandle = 0;
|
||||
private readonly _chatSessionContentProviders = new Map<number, {
|
||||
readonly provider: vscode.ChatSessionContentProvider;
|
||||
readonly extension: IExtensionDescription;
|
||||
readonly capabilities?: vscode.ChatSessionCapabilities;
|
||||
readonly disposable: DisposableStore;
|
||||
}>();
|
||||
private _nextChatSessionItemProviderHandle = 0;
|
||||
private _nextChatSessionItemControllerHandle = 0;
|
||||
private _nextChatSessionContentProviderHandle = 0;
|
||||
|
||||
/**
|
||||
@@ -140,6 +320,52 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
createChatSessionItemController(extension: IExtensionDescription, id: string, refreshHandler: () => Thenable<void>): vscode.ChatSessionItemController {
|
||||
const controllerHandle = this._nextChatSessionItemControllerHandle++;
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// TODO: Currently not hooked up
|
||||
const onDidArchiveChatSessionItem = disposables.add(new Emitter<vscode.ChatSessionItem>());
|
||||
|
||||
const collection = new ChatSessionItemCollectionImpl(() => {
|
||||
this._proxy.$onDidChangeChatSessionItems(controllerHandle);
|
||||
});
|
||||
|
||||
let isDisposed = false;
|
||||
|
||||
const controller: vscode.ChatSessionItemController = {
|
||||
id,
|
||||
refreshHandler,
|
||||
items: collection,
|
||||
onDidArchiveChatSessionItem: onDidArchiveChatSessionItem.event,
|
||||
createChatSessionItem: (resource: vscode.Uri, label: string) => {
|
||||
if (isDisposed) {
|
||||
throw new Error('ChatSessionItemController has been disposed');
|
||||
}
|
||||
|
||||
return new ChatSessionItemImpl(resource, label, () => {
|
||||
// TODO: Optimize to only update the specific item
|
||||
this._proxy.$onDidChangeChatSessionItems(controllerHandle);
|
||||
});
|
||||
},
|
||||
dispose: () => {
|
||||
isDisposed = true;
|
||||
disposables.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
this._chatSessionItemControllers.set(controllerHandle, { controller, extension, disposable: disposables, sessionType: id });
|
||||
this._proxy.$registerChatSessionItemProvider(controllerHandle, id);
|
||||
|
||||
disposables.add(toDisposable(() => {
|
||||
this._chatSessionItemControllers.delete(controllerHandle);
|
||||
this._proxy.$unregisterChatSessionItemProvider(controllerHandle);
|
||||
}));
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
registerChatSessionContentProvider(extension: IExtensionDescription, chatSessionScheme: string, chatParticipant: vscode.ChatParticipant, provider: vscode.ChatSessionContentProvider, capabilities?: vscode.ChatSessionCapabilities): vscode.Disposable {
|
||||
const handle = this._nextChatSessionContentProviderHandle++;
|
||||
const disposables = new DisposableStore();
|
||||
@@ -184,17 +410,25 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
}
|
||||
}
|
||||
|
||||
private convertChatSessionItem(sessionType: string, sessionContent: vscode.ChatSessionItem): IChatSessionItem {
|
||||
private convertChatSessionItem(sessionContent: vscode.ChatSessionItem): IChatSessionItem {
|
||||
// Support both new (created, lastRequestStarted, lastRequestEnded) and old (startTime, endTime) timing properties
|
||||
const timing = sessionContent.timing;
|
||||
const created = timing?.created ?? timing?.startTime ?? 0;
|
||||
const lastRequestStarted = timing?.lastRequestStarted ?? timing?.startTime;
|
||||
const lastRequestEnded = timing?.lastRequestEnded ?? timing?.endTime;
|
||||
|
||||
return {
|
||||
resource: sessionContent.resource,
|
||||
label: sessionContent.label,
|
||||
description: sessionContent.description ? typeConvert.MarkdownString.from(sessionContent.description) : undefined,
|
||||
badge: sessionContent.badge ? typeConvert.MarkdownString.from(sessionContent.badge) : undefined,
|
||||
status: this.convertChatSessionStatus(sessionContent.status),
|
||||
archived: sessionContent.archived,
|
||||
tooltip: typeConvert.MarkdownString.fromStrict(sessionContent.tooltip),
|
||||
timing: {
|
||||
startTime: sessionContent.timing?.startTime ?? 0,
|
||||
endTime: sessionContent.timing?.endTime
|
||||
created,
|
||||
lastRequestStarted,
|
||||
lastRequestEnded,
|
||||
},
|
||||
changes: sessionContent.changes instanceof Array
|
||||
? sessionContent.changes :
|
||||
@@ -207,21 +441,35 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
}
|
||||
|
||||
async $provideChatSessionItems(handle: number, token: vscode.CancellationToken): Promise<IChatSessionItem[]> {
|
||||
const entry = this._chatSessionItemProviders.get(handle);
|
||||
if (!entry) {
|
||||
this._logService.error(`No provider registered for handle ${handle}`);
|
||||
return [];
|
||||
}
|
||||
let items: vscode.ChatSessionItem[];
|
||||
|
||||
const sessions = await entry.provider.provideChatSessionItems(token);
|
||||
if (!sessions) {
|
||||
return [];
|
||||
const controller = this._chatSessionItemControllers.get(handle);
|
||||
if (controller) {
|
||||
// Call the refresh handler to populate items
|
||||
await controller.controller.refreshHandler();
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
items = Array.from(controller.controller.items, x => x[1]);
|
||||
} else {
|
||||
|
||||
const itemProvider = this._chatSessionItemProviders.get(handle);
|
||||
if (!itemProvider) {
|
||||
this._logService.error(`No provider registered for handle ${handle}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
items = await itemProvider.provider.provideChatSessionItems(token) ?? [];
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const response: IChatSessionItem[] = [];
|
||||
for (const sessionContent of sessions) {
|
||||
for (const sessionContent of items) {
|
||||
this._sessionItems.set(sessionContent.resource, sessionContent);
|
||||
response.push(this.convertChatSessionItem(entry.sessionType, sessionContent));
|
||||
response.push(this.convertChatSessionItem(sessionContent));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -359,19 +359,24 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode
|
||||
? { files: changes.files, insertions: changes.insertions, deletions: changes.deletions }
|
||||
: changes;
|
||||
|
||||
// Times: it is important to always provide a start and end time to track
|
||||
// Times: it is important to always provide timing information to track
|
||||
// unread/read state for example.
|
||||
// If somehow the provider does not provide any, fallback to last known
|
||||
let startTime = session.timing.startTime;
|
||||
let endTime = session.timing.endTime;
|
||||
if (!startTime || !endTime) {
|
||||
let created = session.timing.created;
|
||||
let lastRequestStarted = session.timing.lastRequestStarted;
|
||||
let lastRequestEnded = session.timing.lastRequestEnded;
|
||||
if (!created || !lastRequestEnded) {
|
||||
const existing = this._sessions.get(session.resource);
|
||||
if (!startTime && existing?.timing.startTime) {
|
||||
startTime = existing.timing.startTime;
|
||||
if (!created && existing?.timing.created) {
|
||||
created = existing.timing.created;
|
||||
}
|
||||
|
||||
if (!endTime && existing?.timing.endTime) {
|
||||
endTime = existing.timing.endTime;
|
||||
if (!lastRequestEnded && existing?.timing.lastRequestEnded) {
|
||||
lastRequestEnded = existing.timing.lastRequestEnded;
|
||||
}
|
||||
|
||||
if (!lastRequestStarted && existing?.timing.lastRequestStarted) {
|
||||
lastRequestStarted = existing.timing.lastRequestStarted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +391,13 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode
|
||||
tooltip: session.tooltip,
|
||||
status,
|
||||
archived: session.archived,
|
||||
timing: { startTime, endTime, inProgressTime, finishedOrFailedTime },
|
||||
timing: {
|
||||
created,
|
||||
lastRequestStarted,
|
||||
lastRequestEnded,
|
||||
inProgressTime,
|
||||
finishedOrFailedTime
|
||||
},
|
||||
changes: normalizedChanges,
|
||||
}));
|
||||
}
|
||||
@@ -454,7 +465,7 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode
|
||||
private isRead(session: IInternalAgentSessionData): boolean {
|
||||
const readDate = this.sessionStates.get(session.resource)?.read;
|
||||
|
||||
return (readDate ?? AgentSessionsModel.READ_STATE_INITIAL_DATE) >= (session.timing.endTime ?? session.timing.startTime);
|
||||
return (readDate ?? AgentSessionsModel.READ_STATE_INITIAL_DATE) >= (session.timing.lastRequestEnded ?? session.timing.lastRequestStarted ?? session.timing.created);
|
||||
}
|
||||
|
||||
private setRead(session: IInternalAgentSessionData, read: boolean): void {
|
||||
@@ -473,7 +484,7 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode
|
||||
|
||||
//#region Sessions Cache
|
||||
|
||||
interface ISerializedAgentSession extends Omit<IAgentSessionData, 'iconPath' | 'resource' | 'icon'> {
|
||||
interface ISerializedAgentSession {
|
||||
|
||||
readonly providerType: string;
|
||||
readonly providerLabel: string;
|
||||
@@ -492,7 +503,11 @@ interface ISerializedAgentSession extends Omit<IAgentSessionData, 'iconPath' | '
|
||||
readonly archived: boolean | undefined;
|
||||
|
||||
readonly timing: {
|
||||
readonly startTime: number;
|
||||
readonly created: number;
|
||||
readonly lastRequestStarted?: number;
|
||||
readonly lastRequestEnded?: number;
|
||||
// Old format for backward compatibility when reading
|
||||
readonly startTime?: number;
|
||||
readonly endTime?: number;
|
||||
};
|
||||
|
||||
@@ -535,8 +550,9 @@ class AgentSessionsCache {
|
||||
archived: session.archived,
|
||||
|
||||
timing: {
|
||||
startTime: session.timing.startTime,
|
||||
endTime: session.timing.endTime,
|
||||
created: session.timing.created,
|
||||
lastRequestStarted: session.timing.lastRequestStarted,
|
||||
lastRequestEnded: session.timing.lastRequestEnded,
|
||||
},
|
||||
|
||||
changes: session.changes,
|
||||
@@ -553,7 +569,7 @@ class AgentSessionsCache {
|
||||
|
||||
try {
|
||||
const cached = JSON.parse(sessionsCache) as ISerializedAgentSession[];
|
||||
return cached.map(session => ({
|
||||
return cached.map((session): IInternalAgentSessionData => ({
|
||||
providerType: session.providerType,
|
||||
providerLabel: session.providerLabel,
|
||||
|
||||
@@ -569,8 +585,10 @@ class AgentSessionsCache {
|
||||
archived: session.archived,
|
||||
|
||||
timing: {
|
||||
startTime: session.timing.startTime,
|
||||
endTime: session.timing.endTime,
|
||||
// Support loading both new and old cache formats
|
||||
created: session.timing.created ?? session.timing.startTime ?? 0,
|
||||
lastRequestStarted: session.timing.lastRequestStarted ?? session.timing.startTime,
|
||||
lastRequestEnded: session.timing.lastRequestEnded ?? session.timing.endTime,
|
||||
},
|
||||
|
||||
changes: Array.isArray(session.changes) ? session.changes.map((change: IChatSessionFileChange) => ({
|
||||
|
||||
@@ -44,7 +44,7 @@ export const deleteButton: IQuickInputButton = {
|
||||
|
||||
export function getSessionDescription(session: IAgentSession): string {
|
||||
const descriptionText = typeof session.description === 'string' ? session.description : session.description ? renderAsPlaintext(session.description) : undefined;
|
||||
const timeAgo = fromNow(session.timing.endTime || session.timing.startTime);
|
||||
const timeAgo = fromNow(session.timing.lastRequestEnded ?? session.timing.lastRequestStarted ?? session.timing.created);
|
||||
const descriptionParts = [descriptionText, session.providerLabel, timeAgo].filter(part => !!part);
|
||||
|
||||
return descriptionParts.join(' • ');
|
||||
|
||||
@@ -323,7 +323,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
}
|
||||
|
||||
if (!timeLabel) {
|
||||
timeLabel = fromNow(session.timing.endTime || session.timing.startTime);
|
||||
timeLabel = fromNow(session.timing.lastRequestEnded ?? session.timing.lastRequestStarted ?? session.timing.created);
|
||||
}
|
||||
|
||||
return timeLabel;
|
||||
@@ -395,7 +395,8 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
details.push(duration);
|
||||
}
|
||||
} else {
|
||||
details.push(fromNow(session.timing.startTime, true, true));
|
||||
const startTime = session.timing.lastRequestStarted ?? session.timing.created;
|
||||
details.push(fromNow(startTime, true, true));
|
||||
}
|
||||
|
||||
lines.push(details.join(' • '));
|
||||
@@ -567,7 +568,7 @@ export class AgentSessionsAccessibilityProvider implements IListAccessibilityPro
|
||||
return localize('agentSessionSectionAriaLabel', "{0} sessions section", element.label);
|
||||
}
|
||||
|
||||
return localize('agentSessionItemAriaLabel', "{0} session {1} ({2}), created {3}", element.providerLabel, element.label, toStatusLabel(element.status), new Date(element.timing.startTime).toLocaleString());
|
||||
return localize('agentSessionItemAriaLabel', "{0} session {1} ({2}), created {3}", element.providerLabel, element.label, toStatusLabel(element.status), new Date(element.timing.created).toLocaleString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,7 +732,7 @@ export function groupAgentSessions(sessions: IAgentSession[]): Map<AgentSessionS
|
||||
} else if (session.isArchived()) {
|
||||
archivedSessions.push(session);
|
||||
} else {
|
||||
const sessionTime = session.timing.endTime || session.timing.startTime;
|
||||
const sessionTime = session.timing.lastRequestEnded ?? session.timing.lastRequestStarted ?? session.timing.created;
|
||||
if (sessionTime >= startOfToday) {
|
||||
todaySessions.push(session);
|
||||
} else if (sessionTime >= startOfYesterday) {
|
||||
@@ -826,7 +827,9 @@ export class AgentSessionsSorter implements ITreeSorter<IAgentSession> {
|
||||
}
|
||||
|
||||
//Sort by end or start time (most recent first)
|
||||
return (sessionB.timing.endTime || sessionB.timing.startTime) - (sessionA.timing.endTime || sessionA.timing.startTime);
|
||||
const timeA = sessionA.timing.lastRequestEnded ?? sessionA.timing.lastRequestStarted ?? sessionA.timing.created;
|
||||
const timeB = sessionB.timing.lastRequestEnded ?? sessionB.timing.lastRequestStarted ?? sessionB.timing.created;
|
||||
return timeB - timeA;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -991,8 +991,24 @@ export interface IChatSessionStats {
|
||||
}
|
||||
|
||||
export interface IChatSessionTiming {
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
/**
|
||||
* Timestamp when the session was created in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*/
|
||||
created: number;
|
||||
|
||||
/**
|
||||
* Timestamp when the most recent request started in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*
|
||||
* Should be undefined if no requests have been made yet.
|
||||
*/
|
||||
lastRequestStarted: number | undefined;
|
||||
|
||||
/**
|
||||
* Timestamp when the most recent request completed in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*
|
||||
* Should be undefined if the most recent request is still in progress or if no requests have been made yet.
|
||||
*/
|
||||
lastRequestEnded: number | undefined;
|
||||
}
|
||||
|
||||
export const enum ResponseModelState {
|
||||
|
||||
@@ -377,7 +377,11 @@ export class ChatService extends Disposable implements IChatService {
|
||||
...entry,
|
||||
sessionResource,
|
||||
// TODO@roblourens- missing for old data- normalize inside the store
|
||||
timing: entry.timing ?? { startTime: entry.lastMessageDate },
|
||||
timing: entry.timing ?? {
|
||||
created: entry.lastMessageDate,
|
||||
lastRequestStarted: undefined,
|
||||
lastRequestEnded: entry.lastMessageDate,
|
||||
},
|
||||
isActive: this._sessionModels.has(sessionResource),
|
||||
// TODO@roblourens- missing for old data- normalize inside the store
|
||||
lastResponseState: entry.lastResponseState ?? ResponseModelState.Complete,
|
||||
@@ -393,7 +397,11 @@ export class ChatService extends Disposable implements IChatService {
|
||||
...metadata,
|
||||
sessionResource,
|
||||
// TODO@roblourens- missing for old data- normalize inside the store
|
||||
timing: metadata.timing ?? { startTime: metadata.lastMessageDate },
|
||||
timing: metadata.timing ?? {
|
||||
created: metadata.lastMessageDate,
|
||||
lastRequestStarted: undefined,
|
||||
lastRequestEnded: metadata.lastMessageDate,
|
||||
},
|
||||
isActive: this._sessionModels.has(sessionResource),
|
||||
// TODO@roblourens- missing for old data- normalize inside the store
|
||||
lastResponseState: metadata.lastResponseState ?? ResponseModelState.Complete,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta
|
||||
import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './participants/chatAgents.js';
|
||||
import { IChatEditingSession } from './editing/chatEditingService.js';
|
||||
import { IChatModel, IChatRequestVariableData, ISerializableChatModelInputState } from './model/chatModel.js';
|
||||
import { IChatProgress, IChatService } from './chatService/chatService.js';
|
||||
import { IChatProgress, IChatService, IChatSessionTiming } from './chatService/chatService.js';
|
||||
|
||||
export const enum ChatSessionStatus {
|
||||
Failed = 0,
|
||||
@@ -73,6 +73,7 @@ export interface IChatSessionsExtensionPoint {
|
||||
readonly commands?: IChatSessionCommandContribution[];
|
||||
readonly canDelegate?: boolean;
|
||||
}
|
||||
|
||||
export interface IChatSessionItem {
|
||||
resource: URI;
|
||||
label: string;
|
||||
@@ -81,10 +82,7 @@ export interface IChatSessionItem {
|
||||
description?: string | IMarkdownString;
|
||||
status?: ChatSessionStatus;
|
||||
tooltip?: string | IMarkdownString;
|
||||
timing: {
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
};
|
||||
timing: IChatSessionTiming;
|
||||
changes?: {
|
||||
files: number;
|
||||
insertions: number;
|
||||
|
||||
@@ -1802,10 +1802,14 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||
}
|
||||
|
||||
get timing(): IChatSessionTiming {
|
||||
const lastResponse = this._requests.at(-1)?.response;
|
||||
const lastRequest = this._requests.at(-1);
|
||||
const lastResponse = lastRequest?.response;
|
||||
const lastRequestStarted = lastRequest?.timestamp;
|
||||
const lastRequestEnded = lastResponse?.completedAt ?? lastResponse?.timestamp;
|
||||
return {
|
||||
startTime: this._timestamp,
|
||||
endTime: lastResponse?.completedAt ?? lastResponse?.timestamp
|
||||
created: this._timestamp,
|
||||
lastRequestStarted,
|
||||
lastRequestEnded,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -665,12 +665,13 @@ async function getSessionMetadata(session: ChatModel | ISerializableChatData): P
|
||||
session.lastMessageDate :
|
||||
session.requests.at(-1)?.timestamp ?? session.creationDate;
|
||||
|
||||
const timing = session instanceof ChatModel ?
|
||||
const timing: IChatSessionTiming = session instanceof ChatModel ?
|
||||
session.timing :
|
||||
// session is only ISerializableChatData in the old pre-fs storage data migration scenario
|
||||
{
|
||||
startTime: session.creationDate,
|
||||
endTime: lastMessageDate
|
||||
created: session.creationDate,
|
||||
lastRequestStarted: session.requests.at(-1)?.timestamp,
|
||||
lastRequestEnded: lastMessageDate,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -176,8 +176,8 @@ suite('Agent Sessions', () => {
|
||||
|
||||
test('should handle session with all properties', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
const startTime = Date.now();
|
||||
const endTime = startTime + 1000;
|
||||
const created = Date.now();
|
||||
const lastRequestEnded = created + 1000;
|
||||
|
||||
const provider: IChatSessionItemProvider = {
|
||||
chatSessionType: 'test-type',
|
||||
@@ -190,8 +190,8 @@ suite('Agent Sessions', () => {
|
||||
status: ChatSessionStatus.Completed,
|
||||
tooltip: 'Session tooltip',
|
||||
iconPath: ThemeIcon.fromId('check'),
|
||||
timing: { startTime, endTime },
|
||||
changes: { files: 1, insertions: 10, deletions: 5, details: [] }
|
||||
timing: { created, lastRequestStarted: created, lastRequestEnded },
|
||||
changes: { files: 1, insertions: 10, deletions: 5 }
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -210,8 +210,8 @@ suite('Agent Sessions', () => {
|
||||
assert.strictEqual(session.description.value, '**Bold** description');
|
||||
}
|
||||
assert.strictEqual(session.status, ChatSessionStatus.Completed);
|
||||
assert.strictEqual(session.timing.startTime, startTime);
|
||||
assert.strictEqual(session.timing.endTime, endTime);
|
||||
assert.strictEqual(session.timing.created, created);
|
||||
assert.strictEqual(session.timing.lastRequestEnded, lastRequestEnded);
|
||||
assert.deepStrictEqual(session.changes, { files: 1, insertions: 10, deletions: 5 });
|
||||
});
|
||||
});
|
||||
@@ -1521,9 +1521,10 @@ suite('Agent Sessions', () => {
|
||||
test('should consider sessions before initial date as read by default', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
// Session with timing before the READ_STATE_INITIAL_DATE (December 8, 2025)
|
||||
const oldSessionTiming = {
|
||||
startTime: Date.UTC(2025, 10 /* November */, 1),
|
||||
endTime: Date.UTC(2025, 10 /* November */, 2),
|
||||
const oldSessionTiming: IChatSessionItem['timing'] = {
|
||||
created: Date.UTC(2025, 10 /* November */, 1),
|
||||
lastRequestStarted: Date.UTC(2025, 10 /* November */, 1),
|
||||
lastRequestEnded: Date.UTC(2025, 10 /* November */, 2),
|
||||
};
|
||||
|
||||
const provider: IChatSessionItemProvider = {
|
||||
@@ -1552,9 +1553,10 @@ suite('Agent Sessions', () => {
|
||||
test('should consider sessions after initial date as unread by default', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
// Session with timing after the READ_STATE_INITIAL_DATE (December 8, 2025)
|
||||
const newSessionTiming = {
|
||||
startTime: Date.UTC(2025, 11 /* December */, 10),
|
||||
endTime: Date.UTC(2025, 11 /* December */, 11),
|
||||
const newSessionTiming: IChatSessionItem['timing'] = {
|
||||
created: Date.UTC(2025, 11 /* December */, 10),
|
||||
lastRequestStarted: Date.UTC(2025, 11 /* December */, 10),
|
||||
lastRequestEnded: Date.UTC(2025, 11 /* December */, 11),
|
||||
};
|
||||
|
||||
const provider: IChatSessionItemProvider = {
|
||||
@@ -1583,9 +1585,10 @@ suite('Agent Sessions', () => {
|
||||
test('should use endTime for read state comparison when available', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
// Session with startTime before initial date but endTime after
|
||||
const sessionTiming = {
|
||||
startTime: Date.UTC(2025, 10 /* November */, 1),
|
||||
endTime: Date.UTC(2025, 11 /* December */, 10),
|
||||
const sessionTiming: IChatSessionItem['timing'] = {
|
||||
created: Date.UTC(2025, 10 /* November */, 1),
|
||||
lastRequestStarted: Date.UTC(2025, 10 /* November */, 1),
|
||||
lastRequestEnded: Date.UTC(2025, 11 /* December */, 10),
|
||||
};
|
||||
|
||||
const provider: IChatSessionItemProvider = {
|
||||
@@ -1606,7 +1609,7 @@ suite('Agent Sessions', () => {
|
||||
await viewModel.resolve(undefined);
|
||||
|
||||
const session = viewModel.sessions[0];
|
||||
// Should use endTime (December 10) which is after the initial date
|
||||
// Should use lastRequestEnded (December 10) which is after the initial date
|
||||
assert.strictEqual(session.isRead(), false);
|
||||
});
|
||||
});
|
||||
@@ -1614,8 +1617,10 @@ suite('Agent Sessions', () => {
|
||||
test('should use startTime for read state comparison when endTime is not available', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
// Session with only startTime before initial date
|
||||
const sessionTiming = {
|
||||
startTime: Date.UTC(2025, 10 /* November */, 1),
|
||||
const sessionTiming: IChatSessionItem['timing'] = {
|
||||
created: Date.UTC(2025, 10 /* November */, 1),
|
||||
lastRequestStarted: Date.UTC(2025, 10 /* November */, 1),
|
||||
lastRequestEnded: undefined,
|
||||
};
|
||||
|
||||
const provider: IChatSessionItemProvider = {
|
||||
@@ -2054,8 +2059,15 @@ function makeSimpleSessionItem(id: string, overrides?: Partial<IChatSessionItem>
|
||||
};
|
||||
}
|
||||
|
||||
function makeNewSessionTiming(): IChatSessionItem['timing'] {
|
||||
function makeNewSessionTiming(options?: {
|
||||
created?: number;
|
||||
lastRequestStarted?: number | undefined;
|
||||
lastRequestEnded?: number | undefined;
|
||||
}): IChatSessionItem['timing'] {
|
||||
const now = Date.now();
|
||||
return {
|
||||
startTime: Date.now(),
|
||||
created: options?.created ?? now,
|
||||
lastRequestStarted: options?.lastRequestStarted,
|
||||
lastRequestEnded: options?.lastRequestEnded,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,8 +36,9 @@ suite('AgentSessionsDataSource', () => {
|
||||
label: `Session ${overrides.id ?? 'default'}`,
|
||||
icon: Codicon.terminal,
|
||||
timing: {
|
||||
startTime: overrides.startTime ?? now,
|
||||
endTime: overrides.endTime ?? now,
|
||||
created: overrides.startTime ?? now,
|
||||
lastRequestEnded: undefined,
|
||||
lastRequestStarted: undefined,
|
||||
},
|
||||
isArchived: () => overrides.isArchived ?? false,
|
||||
setArchived: () => { },
|
||||
@@ -73,8 +74,8 @@ suite('AgentSessionsDataSource', () => {
|
||||
return {
|
||||
compare: (a, b) => {
|
||||
// Sort by end time, most recent first
|
||||
const aTime = a.timing.endTime || a.timing.startTime;
|
||||
const bTime = b.timing.endTime || b.timing.startTime;
|
||||
const aTime = a.timing.lastRequestEnded ?? a.timing.lastRequestStarted ?? a.timing.created;
|
||||
const bTime = b.timing.lastRequestEnded ?? b.timing.lastRequestStarted ?? b.timing.created;
|
||||
return bTime - aTime;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,11 +18,24 @@ import { LocalAgentsSessionsProvider } from '../../../browser/agentSessions/loca
|
||||
import { ModifiedFileEntryState } from '../../../common/editing/chatEditingService.js';
|
||||
import { IChatModel, IChatRequestModel, IChatResponseModel } from '../../../common/model/chatModel.js';
|
||||
import { IChatDetail, IChatService, IChatSessionStartOptions, ResponseModelState } from '../../../common/chatService/chatService.js';
|
||||
import { ChatSessionStatus, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js';
|
||||
import { ChatSessionStatus, IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js';
|
||||
import { LocalChatSessionUri } from '../../../common/model/chatUri.js';
|
||||
import { ChatAgentLocation } from '../../../common/constants.js';
|
||||
import { MockChatSessionsService } from '../../common/mockChatSessionsService.js';
|
||||
|
||||
function createTestTiming(options?: {
|
||||
created?: number;
|
||||
lastRequestStarted?: number | undefined;
|
||||
lastRequestEnded?: number | undefined;
|
||||
}): IChatSessionItem['timing'] {
|
||||
const now = Date.now();
|
||||
return {
|
||||
created: options?.created ?? now,
|
||||
lastRequestStarted: options?.lastRequestStarted,
|
||||
lastRequestEnded: options?.lastRequestEnded,
|
||||
};
|
||||
}
|
||||
|
||||
class MockChatService implements IChatService {
|
||||
private readonly _chatModels: ISettableObservable<Iterable<IChatModel>> = observableValue('chatModels', []);
|
||||
readonly chatModels = this._chatModels;
|
||||
@@ -319,7 +332,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
title: 'Test Session',
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
timing: { startTime: 0, endTime: 1 },
|
||||
timing: createTestTiming(),
|
||||
lastResponseState: ResponseModelState.Complete
|
||||
}]);
|
||||
|
||||
@@ -343,7 +356,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now() - 10000,
|
||||
isActive: false,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 }
|
||||
timing: createTestTiming()
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
@@ -369,7 +382,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 }
|
||||
timing: createTestTiming()
|
||||
}]);
|
||||
mockChatService.setHistorySessionItems([{
|
||||
sessionResource,
|
||||
@@ -377,7 +390,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now() - 10000,
|
||||
isActive: false,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 }
|
||||
timing: createTestTiming()
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
@@ -405,7 +418,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 }
|
||||
timing: createTestTiming()
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
@@ -435,7 +448,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 },
|
||||
timing: createTestTiming(),
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
@@ -464,7 +477,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 },
|
||||
timing: createTestTiming(),
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
@@ -493,7 +506,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 },
|
||||
timing: createTestTiming(),
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
@@ -537,7 +550,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 },
|
||||
timing: createTestTiming(),
|
||||
stats: {
|
||||
added: 30,
|
||||
removed: 8,
|
||||
@@ -582,7 +595,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 }
|
||||
timing: createTestTiming()
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
@@ -593,7 +606,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
});
|
||||
|
||||
suite('Session Timing', () => {
|
||||
test('should use model timestamp for startTime when model exists', async () => {
|
||||
test('should use model timestamp for created when model exists', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
const provider = createProvider();
|
||||
|
||||
@@ -612,16 +625,16 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: modelTimestamp }
|
||||
timing: createTestTiming({ created: modelTimestamp })
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
assert.strictEqual(sessions.length, 1);
|
||||
assert.strictEqual(sessions[0].timing.startTime, modelTimestamp);
|
||||
assert.strictEqual(sessions[0].timing.created, modelTimestamp);
|
||||
});
|
||||
});
|
||||
|
||||
test('should use lastMessageDate for startTime when model does not exist', async () => {
|
||||
test('should use lastMessageDate for created when model does not exist', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
const provider = createProvider();
|
||||
|
||||
@@ -635,16 +648,16 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate,
|
||||
isActive: false,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: lastMessageDate }
|
||||
timing: createTestTiming({ created: lastMessageDate })
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
assert.strictEqual(sessions.length, 1);
|
||||
assert.strictEqual(sessions[0].timing.startTime, lastMessageDate);
|
||||
assert.strictEqual(sessions[0].timing.created, lastMessageDate);
|
||||
});
|
||||
});
|
||||
|
||||
test('should set endTime from last response completedAt', async () => {
|
||||
test('should set lastRequestEnded from last response completedAt', async () => {
|
||||
return runWithFakedTimers({}, async () => {
|
||||
const provider = createProvider();
|
||||
|
||||
@@ -664,12 +677,12 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: completedAt }
|
||||
timing: createTestTiming({ lastRequestEnded: completedAt })
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
assert.strictEqual(sessions.length, 1);
|
||||
assert.strictEqual(sessions[0].timing.endTime, completedAt);
|
||||
assert.strictEqual(sessions[0].timing.lastRequestEnded, completedAt);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -692,7 +705,7 @@ suite('LocalAgentsSessionsProvider', () => {
|
||||
lastMessageDate: Date.now(),
|
||||
isActive: true,
|
||||
lastResponseState: ResponseModelState.Complete,
|
||||
timing: { startTime: 0, endTime: 1 }
|
||||
timing: createTestTiming()
|
||||
}]);
|
||||
|
||||
const sessions = await provider.provideChatSessionItems(CancellationToken.None);
|
||||
|
||||
@@ -10,13 +10,14 @@ import { URI } from '../../../../../../base/common/uri.js';
|
||||
import { IChatEditingSession } from '../../../common/editing/chatEditingService.js';
|
||||
import { IChatChangeEvent, IChatModel, IChatRequestModel, IChatRequestNeedsInputInfo, IExportableChatData, IExportableRepoData, IInputModel, ISerializableChatData } from '../../../common/model/chatModel.js';
|
||||
import { ChatAgentLocation } from '../../../common/constants.js';
|
||||
import { IChatSessionTiming } from '../../../common/chatService/chatService.js';
|
||||
|
||||
export class MockChatModel extends Disposable implements IChatModel {
|
||||
readonly onDidDispose = this._register(new Emitter<void>()).event;
|
||||
readonly onDidChange = this._register(new Emitter<IChatChangeEvent>()).event;
|
||||
sessionId = '';
|
||||
readonly timestamp = 0;
|
||||
readonly timing = { startTime: 0 };
|
||||
readonly timing: IChatSessionTiming = { created: Date.now(), lastRequestStarted: undefined, lastRequestEnded: undefined };
|
||||
readonly initialLocation = ChatAgentLocation.Chat;
|
||||
readonly title = '';
|
||||
readonly hasCustomTitle = false;
|
||||
|
||||
@@ -26,6 +26,25 @@ declare module 'vscode' {
|
||||
InProgress = 2
|
||||
}
|
||||
|
||||
export namespace chat {
|
||||
/**
|
||||
* Registers a new {@link ChatSessionItemProvider chat session item provider}.
|
||||
*
|
||||
* To use this, also make sure to also add `chatSessions` contribution in the `package.json`.
|
||||
*
|
||||
* @param chatSessionType The type of chat session the provider is for.
|
||||
* @param provider The provider to register.
|
||||
*
|
||||
* @returns A disposable that unregisters the provider when disposed.
|
||||
*/
|
||||
export function registerChatSessionItemProvider(chatSessionType: string, provider: ChatSessionItemProvider): Disposable;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ChatSessionItemController chat session item controller} with the given unique identifier.
|
||||
*/
|
||||
export function createChatSessionItemController(id: string, refreshHandler: () => Thenable<void>): ChatSessionItemController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of information about chat sessions.
|
||||
*/
|
||||
@@ -52,6 +71,86 @@ declare module 'vscode' {
|
||||
// #endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of information about chat sessions.
|
||||
*/
|
||||
export interface ChatSessionItemController {
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* Unregisters the controller, disposing of its associated chat session items.
|
||||
*/
|
||||
dispose(): void;
|
||||
|
||||
/**
|
||||
* Managed collection of chat session items
|
||||
*/
|
||||
readonly items: ChatSessionItemCollection;
|
||||
|
||||
/**
|
||||
* Creates a new managed chat session item that be added to the collection.
|
||||
*/
|
||||
createChatSessionItem(resource: Uri, label: string): ChatSessionItem;
|
||||
|
||||
/**
|
||||
* Handler called to refresh the collection of chat session items.
|
||||
*
|
||||
* This is also called on first load to get the initial set of items.
|
||||
*/
|
||||
refreshHandler: () => Thenable<void>;
|
||||
|
||||
/**
|
||||
* Fired when an item is archived by the editor
|
||||
*
|
||||
* TODO: expose archive state on the item too?
|
||||
*/
|
||||
readonly onDidArchiveChatSessionItem: Event<ChatSessionItem>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of chat session items. It provides operations for managing and iterating over the items.
|
||||
*/
|
||||
export interface ChatSessionItemCollection extends Iterable<readonly [id: Uri, chatSessionItem: ChatSessionItem]> {
|
||||
/**
|
||||
* Gets the number of items in the collection.
|
||||
*/
|
||||
readonly size: number;
|
||||
|
||||
/**
|
||||
* Replaces the items stored by the collection.
|
||||
* @param items Items to store.
|
||||
*/
|
||||
replace(items: readonly ChatSessionItem[]): void;
|
||||
|
||||
/**
|
||||
* Iterate over each entry in this collection.
|
||||
*
|
||||
* @param callback Function to execute for each entry.
|
||||
* @param thisArg The `this` context used when invoking the handler function.
|
||||
*/
|
||||
forEach(callback: (item: ChatSessionItem, collection: ChatSessionItemCollection) => unknown, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* Adds the chat session item to the collection. If an item with the same resource URI already
|
||||
* exists, it'll be replaced.
|
||||
* @param item Item to add.
|
||||
*/
|
||||
add(item: ChatSessionItem): void;
|
||||
|
||||
/**
|
||||
* Removes a single chat session item from the collection.
|
||||
* @param resource Item resource to delete.
|
||||
*/
|
||||
delete(resource: Uri): void;
|
||||
|
||||
/**
|
||||
* Efficiently gets a chat session item by resource, if it exists, in the collection.
|
||||
* @param resource Item resource to get.
|
||||
* @returns The found item or undefined if it does not exist.
|
||||
*/
|
||||
get(resource: Uri): ChatSessionItem | undefined;
|
||||
}
|
||||
|
||||
export interface ChatSessionItem {
|
||||
/**
|
||||
* The resource associated with the chat session.
|
||||
@@ -91,15 +190,42 @@ declare module 'vscode' {
|
||||
tooltip?: string | MarkdownString;
|
||||
|
||||
/**
|
||||
* The times at which session started and ended
|
||||
* Whether the chat session has been archived.
|
||||
*/
|
||||
archived?: boolean;
|
||||
|
||||
/**
|
||||
* Timing information for the chat session
|
||||
*/
|
||||
timing?: {
|
||||
/**
|
||||
* Session start timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
* Timestamp when the session was created in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*/
|
||||
startTime: number;
|
||||
created: number;
|
||||
|
||||
/**
|
||||
* Timestamp when the most recent request started in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*
|
||||
* Should be undefined if no requests have been made yet.
|
||||
*/
|
||||
lastRequestStarted?: number;
|
||||
|
||||
/**
|
||||
* Timestamp when the most recent request completed in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*
|
||||
* Should be undefined if the most recent request is still in progress or if no requests have been made yet.
|
||||
*/
|
||||
lastRequestEnded?: number;
|
||||
|
||||
/**
|
||||
* Session start timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
* @deprecated Use `created` and `lastRequestStarted` instead.
|
||||
*/
|
||||
startTime?: number;
|
||||
|
||||
/**
|
||||
* Session end timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
* @deprecated Use `lastRequestEnded` instead.
|
||||
*/
|
||||
endTime?: number;
|
||||
};
|
||||
@@ -268,18 +394,6 @@ declare module 'vscode' {
|
||||
}
|
||||
|
||||
export namespace chat {
|
||||
/**
|
||||
* Registers a new {@link ChatSessionItemProvider chat session item provider}.
|
||||
*
|
||||
* To use this, also make sure to also add `chatSessions` contribution in the `package.json`.
|
||||
*
|
||||
* @param chatSessionType The type of chat session the provider is for.
|
||||
* @param provider The provider to register.
|
||||
*
|
||||
* @returns A disposable that unregisters the provider when disposed.
|
||||
*/
|
||||
export function registerChatSessionItemProvider(chatSessionType: string, provider: ChatSessionItemProvider): Disposable;
|
||||
|
||||
/**
|
||||
* Registers a new {@link ChatSessionContentProvider chat session content provider}.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user