2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import { equals as arraysEqual } from '../../../../../base/common/arrays.js';
7
>
import { Emitter, Event } from '../../../../../base/common/event.js';
8
>
import { Disposable } from '../../../../../base/common/lifecycle.js';
9
>
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
10
>
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
11
>
import { Memento } from '../../../../common/memento.js';
12
>
import { IChatModelInputState } from '../model/chatModel.js';
13
>
import { CHAT_PROVIDER_ID } from '../participants/chatParticipantContribTypes.js';
14
>
import { IChatRequestVariableEntry } from '../attachments/chatVariableEntries.js';
15
>
import { ChatAgentLocation, ChatModeKind } from '../constants.js';
16
>
17
>
interface IChatHistoryEntry {
18
>
text: string;
19
>
state?: IChatInputState;
20
>
}
21
>
22
>
/** The collected input state for chat history entries */
23
>
interface IChatInputState {
24
>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25
>
[key: string]: any;
26
>
chatContextAttachments?: ReadonlyArray<IChatRequestVariableEntry>;
27
>
28
>
/**
29
>
* This should be a mode id (ChatMode | string).
30
>
* { id: string } is the old IChatMode. This is deprecated but may still be in persisted data.
31
>
*/
32
>
chatMode?: ChatModeKind | string | { id: string };
33
>
}
34
>
35
>
export const IChatWidgetHistoryService = createDecorator<IChatWidgetHistoryService>('IChatWidgetHistoryService');
36
>
export interface IChatWidgetHistoryService {
37
>
_serviceBrand: undefined;
38
>
39
>
readonly onDidChangeHistory: Event<ChatHistoryChange>;
40
>
41
>
clearHistory(): void;
42
>
getHistory(location: ChatAgentLocation, historyKey?: string): readonly IChatModelInputState[];
43
>
append(location: ChatAgentLocation, history: IChatModelInputState, historyKey?: string): void;
44
>
moveHistory(location: ChatAgentLocation, fromHistoryKey: string, toHistoryKey: string): void;
45
>
}
46
>
47
>
interface IChatHistory {
48
>
history?: { [providerId: string]: IChatModelInputState[] };
49
>
}
50
>
51
>
export type ChatHistoryChange = { kind: 'append'; location: ChatAgentLocation; historyKey: string | undefined; entry: IChatModelInputState } | { kind: 'move'; location: ChatAgentLocation; fromHistoryKey: string; toHistoryKey: string } | { kind: 'clear' };
52
>
53
>
export const ChatInputHistoryMaxEntries = 40;
54
>
55
>
export class ChatWidgetHistoryService extends Disposable implements IChatWidgetHistoryService {
56
>
_serviceBrand: undefined;
57
>
58
>
private memento: Memento<IChatHistory>;
59
>
private viewState: IChatHistory;
60
>
61
>
private readonly _onDidChangeHistory = this._register(new Emitter<ChatHistoryChange>());
62
>
private changed = false;
63
>
readonly onDidChangeHistory = this._onDidChangeHistory.event;
64
>
65
>
constructor(
66
>
@IStorageService storageService: IStorageService
67
>
) {
68
>
super();
69
>
70
>
this.memento = new Memento<IChatHistory>('interactive-session', storageService);
71
>
const loadedState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);
72
>
this.viewState = loadedState;
73
>
74
>
this._register(storageService.onWillSaveState(() => {
75
if (this.changed) {
76
this.memento.saveMemento();
77
this.changed = false;
78
}
80
>
}
81
>
82
>
getHistory(location: ChatAgentLocation, historyKey?: string): IChatModelInputState[] {
83
const key = this.getKey(location, historyKey);
84
const history = this.viewState.history?.[key] ?? [];
85
return history.map(entry => this.migrateHistoryEntry(entry));
86
}
88
>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
89
>
private migrateHistoryEntry(entry: any): IChatModelInputState {
90
// If it's already in the new format (has 'inputText' property), return as-is
91
if (entry.inputText !== undefined) {