Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/agentHost/test/common/sessionTestHelpers.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
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 type { IReference } from '../../../../base/common/lifecycle.js';
7
import { Schemas } from '../../../../base/common/network.js';
8
import { URI } from '../../../../base/common/uri.js';
9
import type { IDiffComputeService, IDiffCountResult } from '../../common/diffComputeService.js';
10
import type { IFileEditContent, IFileEditRecord, ISessionDatabase, ISessionDataService } from '../../common/sessionDataService.js';
11
12
export class TestSessionDatabase implements ISessionDatabase {
13
private readonly _edits: (IFileEditRecord & IFileEditContent)[] = [];
14
private readonly _metadata = new Map<string, string>();
15
16
getAllFileEditsCalls = 0;
17
getFileEditsByTurnCalls = 0;
18
19
addEdit(edit: IFileEditRecord & IFileEditContent): void {
20
this._edits.push(edit);
21
}
22
23
async createTurn(): Promise<void> { }
24
25
async deleteTurn(turnId: string): Promise<void> {
26
for (let i = this._edits.length - 1; i >= 0; i--) {
27
if (this._edits[i].turnId === turnId) {
28
this._edits.splice(i, 1);
29
}
30
}
31
}
32
33
async storeFileEdit(edit: IFileEditRecord & IFileEditContent): Promise<void> {
34
const existingIndex = this._edits.findIndex(e => e.toolCallId === edit.toolCallId && e.filePath === edit.filePath);
35
if (existingIndex >= 0) {
36
this._edits[existingIndex] = edit;
37
} else {
38
this._edits.push(edit);
39
}
40
}
41
42
async getFileEdits(toolCallIds: string[]): Promise<IFileEditRecord[]> {
43
const toolCallIdsSet = new Set(toolCallIds);
44
return this._toEditRecords(this._edits.filter(e => toolCallIdsSet.has(e.toolCallId)));
45
}
46
47
async getAllFileEdits(): Promise<IFileEditRecord[]> {
48
this.getAllFileEditsCalls++;
49
return this._toEditRecords(this._edits);
50
}
51
52
async getFileEditsByTurn(turnId: string): Promise<IFileEditRecord[]> {
53
this.getFileEditsByTurnCalls++;
54
return this._toEditRecords(this._edits.filter(e => e.turnId === turnId));
55
}
56
57
async readFileEditContent(toolCallId: string, filePath: string): Promise<IFileEditContent | undefined> {
58
return this._edits.find(e => e.toolCallId === toolCallId && e.filePath === filePath);
59
}
60
61
async getMetadata(key: string): Promise<string | undefined> {
62
return this._metadata.get(key);
63
}
64
65
async getMetadataObject<T extends Record<string, unknown>>(obj: T): Promise<{ [K in keyof T]: string | undefined }> {
66
return Object.fromEntries(Object.keys(obj).map(key => [key, this._metadata.get(key)])) as { [K in keyof T]: string | undefined };
67
}
68
69
async setMetadata(key: string, value: string): Promise<void> {
70
this._metadata.set(key, value);
71
}
72
73
async close(): Promise<void> { }
74
75
async vacuumInto(_targetPath: string): Promise<void> { }
76
77
dispose(): void { }
78
79
async setTurnEventId(_turnId: string, _eventId: string): Promise<void> { }
80
81
async getTurnEventId(_turnId: string): Promise<string | undefined> { return undefined; }
82
83
async getNextTurnEventId(_turnId: string): Promise<string | undefined> { return undefined; }
84
85
async getFirstTurnEventId(): Promise<string | undefined> { return undefined; }
86
87
async truncateFromTurn(_turnId: string): Promise<void> { }
88
89
async deleteTurnsAfter(_turnId: string): Promise<void> { }
90
91
async deleteAllTurns(): Promise<void> { }
92
93
async remapTurnIds(_mapping: ReadonlyMap<string, string>): Promise<void> { }
94
95
async whenIdle(): Promise<void> { }
96
97
private _toEditRecords(edits: (IFileEditRecord & IFileEditContent)[]): IFileEditRecord[] {
98
return edits.map(({ beforeContent: _, afterContent: _2, ...metadata }) => metadata);
99
}
100
}
101
102
export class TestDiffComputeService implements IDiffComputeService {
103
declare readonly _serviceBrand: undefined;
104
105
callCount = 0;
106
107
constructor(private readonly _result?: IDiffCountResult) { }
108
109
async computeDiffCounts(original: string, modified: string): Promise<IDiffCountResult> {
110
this.callCount++;
111
if (this._result) {
112
return this._result;
113
}
114
115
const originalLines = original ? original.split('\n') : [];
116
const modifiedLines = modified ? modified.split('\n') : [];
117
return {
118
added: Math.max(0, modifiedLines.length - originalLines.length),
119
removed: Math.max(0, originalLines.length - modifiedLines.length),
120
};
121
}
122
}
123
124
export function createZeroDiffComputeService(): IDiffComputeService {
125
return new TestDiffComputeService({ added: 0, removed: 0 });
126
}
127
128
export function createSessionDataService(database: ISessionDatabase = new TestSessionDatabase()): ISessionDataService {
129
return {
130
_serviceBrand: undefined,
131
getSessionDataDir: session => URI.from({ scheme: Schemas.inMemory, path: `/session-data${session.path}` }),
132
getSessionDataDirById: sessionId => URI.from({ scheme: Schemas.inMemory, path: `/session-data/${sessionId}` }),
133
openDatabase: () => createReference(database),
134
tryOpenDatabase: async () => createReference(database),
135
deleteSessionData: async () => { },
136
cleanupOrphanedData: async () => { },
137
whenIdle: async () => { },
138
};
139
}
140
141
export function createNullSessionDataService(): ISessionDataService {
142
return {
143
_serviceBrand: undefined,
144
getSessionDataDir: session => URI.from({ scheme: Schemas.inMemory, path: `/session-data${session.path}` }),
145
getSessionDataDirById: sessionId => URI.from({ scheme: Schemas.inMemory, path: `/session-data/${sessionId}` }),
146
openDatabase: () => { throw new Error('not implemented'); },
147
tryOpenDatabase: async () => undefined,
148
deleteSessionData: async () => { },
149
cleanupOrphanedData: async () => { },
150
whenIdle: async () => { },
151
};
152
}
153
154
export function encodeString(text: string): Uint8Array {
155
return new TextEncoder().encode(text);
156
}
157
158
/**
159
* Returns a no-op {@link IAgentHostGitService} suitable for tests that
160
* exercise the {@link AgentService} but don't care about git state.
161
* Tests that DO care about git state should pass their own implementation.
162
*/
163
export function createNoopGitService(): import('../../node/agentHostGitService.js').IAgentHostGitService {
164
return {
165
_serviceBrand: undefined,
166
isInsideWorkTree: async () => false,
167
getCurrentBranch: async () => undefined,
168
getDefaultBranch: async () => undefined,
169
getBranches: async () => [],
170
getRepositoryRoot: async () => undefined,
171
getWorktreeRoots: async () => [],
172
addWorktree: async () => { },
173
addExistingWorktree: async () => { },
174
removeWorktree: async () => { },
175
branchExists: async () => false,
176
hasUncommittedChanges: async () => false,
177
getSessionGitState: async () => undefined,
178
computeSessionFileDiffs: async () => undefined,
179
showBlob: async () => undefined,
180
};
181
}
182
183
function createReference<T>(object: T): IReference<T> {
184
return {
185
object,
186
dispose: () => { },
187
};
188
}
189
190