Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/intents/node/test/agentSummarizeCommand.spec.ts
13405 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 { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
7
import { IChatMLFetcher } from '../../../../platform/chat/common/chatMLFetcher';
8
import { ChatLocation } from '../../../../platform/chat/common/commonTypes';
9
import { StaticChatMLFetcher } from '../../../../platform/chat/test/common/staticChatMLFetcher';
10
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
11
import { ITestingServicesAccessor } from '../../../../platform/test/node/services';
12
import { TestWorkspaceService } from '../../../../platform/test/node/testWorkspaceService';
13
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
14
import { NullWorkspaceFileIndex } from '../../../../platform/workspaceChunkSearch/node/nullWorkspaceFileIndex';
15
import { IWorkspaceFileIndex } from '../../../../platform/workspaceChunkSearch/node/workspaceFileIndex';
16
import { Event } from '../../../../util/vs/base/common/event';
17
import { URI } from '../../../../util/vs/base/common/uri';
18
import { SyncDescriptor } from '../../../../util/vs/platform/instantiation/common/descriptors';
19
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
20
import { LanguageModelTextPart, LanguageModelToolResult } from '../../../../vscodeTypes';
21
import { Conversation, ICopilotChatResultIn, Turn, TurnStatus } from '../../../prompt/common/conversation';
22
import { IToolCall } from '../../../prompt/common/intents';
23
import { ToolCallRound } from '../../../prompt/common/toolCallRound';
24
import { ChatTelemetryBuilder } from '../../../prompt/node/chatParticipantTelemetry';
25
import { createExtensionUnitTestingServices } from '../../../test/node/services';
26
import { MockChatResponseStream, TestChatRequest } from '../../../test/node/testHelpers';
27
import { ToolName } from '../../../tools/common/toolNames';
28
import { AgentIntent } from '../agentIntent';
29
30
describe('AgentIntent /summarize command', () => {
31
let accessor: ITestingServicesAccessor;
32
let instantiationService: IInstantiationService;
33
let configService: IConfigurationService;
34
let chatResponse: string[] = [];
35
36
beforeAll(() => {
37
const services = createExtensionUnitTestingServices();
38
services.define(IWorkspaceFileIndex, new SyncDescriptor(NullWorkspaceFileIndex));
39
services.define(IWorkspaceService, new SyncDescriptor(
40
TestWorkspaceService,
41
[
42
[URI.file('/workspace')],
43
[]
44
]
45
));
46
chatResponse = [];
47
services.define(IChatMLFetcher, new StaticChatMLFetcher(chatResponse));
48
accessor = services.createTestingAccessor();
49
instantiationService = accessor.get(IInstantiationService);
50
configService = accessor.get(IConfigurationService);
51
});
52
53
afterAll(() => {
54
accessor.dispose();
55
});
56
57
beforeEach(() => {
58
// Reset config to enabled by default
59
configService.setConfig(ConfigKey.SummarizeAgentConversationHistory, true);
60
chatResponse.length = 0;
61
chatResponse.push('This is a test summary of the conversation.');
62
});
63
64
const token = {
65
isCancellationRequested: false,
66
onCancellationRequested: Event.None,
67
};
68
69
async function runSummarize(conversation: Conversation) {
70
const intent = instantiationService.createInstance(AgentIntent);
71
const request = new TestChatRequest('');
72
request.command = 'compact';
73
const stream = new MockChatResponseStream();
74
75
const chatTelemetry = instantiationService.createInstance(
76
ChatTelemetryBuilder,
77
Date.now(),
78
'sessionId',
79
undefined,
80
true,
81
request,
82
undefined,
83
);
84
85
const result = await intent.handleRequest(
86
conversation,
87
request,
88
stream,
89
token,
90
undefined,
91
'agent',
92
ChatLocation.Agent,
93
chatTelemetry,
94
() => false
95
);
96
97
return { result, stream };
98
}
99
100
function createEditFileToolCall(idx: number): IToolCall {
101
return {
102
id: `tooluse_${idx}`,
103
name: ToolName.EditFile,
104
arguments: JSON.stringify({
105
filePath: '/workspace/file.ts',
106
code: `console.log('edit ${idx}')`
107
})
108
};
109
}
110
111
function createEditFileToolResult(...idxs: number[]): Record<string, LanguageModelToolResult> {
112
const result: Record<string, LanguageModelToolResult> = {};
113
for (const idx of idxs) {
114
result[`tooluse_${idx}`] = new LanguageModelToolResult([new LanguageModelTextPart('success')]);
115
}
116
return result;
117
}
118
119
function createConversationWithHistory(): Conversation {
120
// Create a previous turn with tool call rounds
121
const previousTurn = new Turn('turn1', { type: 'user', message: 'Create a file for me' });
122
const previousTurnResult: ICopilotChatResultIn = {
123
metadata: {
124
toolCallRounds: [
125
new ToolCallRound('Created the file', [createEditFileToolCall(1)], undefined, 'toolCallRoundId1'),
126
],
127
toolCallResults: createEditFileToolResult(1),
128
}
129
};
130
previousTurn.setResponse(TurnStatus.Success, { type: 'model', message: 'Done!' }, 'responseId1', previousTurnResult);
131
132
// Create the current turn (the /summarize command turn)
133
const currentTurn = new Turn('turn2', { type: 'user', message: '/summarize' });
134
135
return new Conversation('sessionId', [previousTurn, currentTurn]);
136
}
137
138
function createConversationWithNoHistory(): Conversation {
139
// Just the current /summarize turn, no prior history
140
const currentTurn = new Turn('turn1', { type: 'user', message: '/summarize' });
141
return new Conversation('sessionId', [currentTurn]);
142
}
143
144
test('returns summary metadata when enabled and history exists', async () => {
145
const conversation = createConversationWithHistory();
146
const { result } = await runSummarize(conversation);
147
148
// Should have summary metadata
149
expect(result.metadata).toBeDefined();
150
expect(result.metadata?.summary).toBeDefined();
151
expect(result.metadata?.summary?.toolCallRoundId).toBe('toolCallRoundId1');
152
expect(result.metadata?.summary?.text).toBeTruthy();
153
});
154
155
test('summarizes even when auto-summarize setting is disabled', async () => {
156
// Disable auto-summarization - /compact should still work since it's an explicit user action
157
configService.setConfig(ConfigKey.SummarizeAgentConversationHistory, false);
158
const conversation = createConversationWithHistory();
159
const { result } = await runSummarize(conversation);
160
161
// Should still have summary metadata since /compact is explicit
162
expect(result.metadata).toBeDefined();
163
expect(result.metadata?.summary).toBeDefined();
164
expect(result.metadata?.summary?.toolCallRoundId).toBe('toolCallRoundId1');
165
expect(result.metadata?.summary?.text).toBeTruthy();
166
});
167
168
test('returns friendly message when no history exists', async () => {
169
const conversation = createConversationWithNoHistory();
170
const { result, stream } = await runSummarize(conversation);
171
172
// Should not have summary metadata
173
expect(result.metadata?.summary).toBeUndefined();
174
175
// Should have output with "Nothing to compact" message
176
expect(stream.output.some(msg => msg.toLowerCase().includes('nothing to compact'))).toBe(true);
177
});
178
});
179
180