Path: blob/main/extensions/copilot/src/extension/intents/node/test/agentSummarizeCommand.spec.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';6import { IChatMLFetcher } from '../../../../platform/chat/common/chatMLFetcher';7import { ChatLocation } from '../../../../platform/chat/common/commonTypes';8import { StaticChatMLFetcher } from '../../../../platform/chat/test/common/staticChatMLFetcher';9import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';10import { ITestingServicesAccessor } from '../../../../platform/test/node/services';11import { TestWorkspaceService } from '../../../../platform/test/node/testWorkspaceService';12import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';13import { NullWorkspaceFileIndex } from '../../../../platform/workspaceChunkSearch/node/nullWorkspaceFileIndex';14import { IWorkspaceFileIndex } from '../../../../platform/workspaceChunkSearch/node/workspaceFileIndex';15import { Event } from '../../../../util/vs/base/common/event';16import { URI } from '../../../../util/vs/base/common/uri';17import { SyncDescriptor } from '../../../../util/vs/platform/instantiation/common/descriptors';18import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';19import { LanguageModelTextPart, LanguageModelToolResult } from '../../../../vscodeTypes';20import { Conversation, ICopilotChatResultIn, Turn, TurnStatus } from '../../../prompt/common/conversation';21import { IToolCall } from '../../../prompt/common/intents';22import { ToolCallRound } from '../../../prompt/common/toolCallRound';23import { ChatTelemetryBuilder } from '../../../prompt/node/chatParticipantTelemetry';24import { createExtensionUnitTestingServices } from '../../../test/node/services';25import { MockChatResponseStream, TestChatRequest } from '../../../test/node/testHelpers';26import { ToolName } from '../../../tools/common/toolNames';27import { AgentIntent } from '../agentIntent';2829describe('AgentIntent /summarize command', () => {30let accessor: ITestingServicesAccessor;31let instantiationService: IInstantiationService;32let configService: IConfigurationService;33let chatResponse: string[] = [];3435beforeAll(() => {36const services = createExtensionUnitTestingServices();37services.define(IWorkspaceFileIndex, new SyncDescriptor(NullWorkspaceFileIndex));38services.define(IWorkspaceService, new SyncDescriptor(39TestWorkspaceService,40[41[URI.file('/workspace')],42[]43]44));45chatResponse = [];46services.define(IChatMLFetcher, new StaticChatMLFetcher(chatResponse));47accessor = services.createTestingAccessor();48instantiationService = accessor.get(IInstantiationService);49configService = accessor.get(IConfigurationService);50});5152afterAll(() => {53accessor.dispose();54});5556beforeEach(() => {57// Reset config to enabled by default58configService.setConfig(ConfigKey.SummarizeAgentConversationHistory, true);59chatResponse.length = 0;60chatResponse.push('This is a test summary of the conversation.');61});6263const token = {64isCancellationRequested: false,65onCancellationRequested: Event.None,66};6768async function runSummarize(conversation: Conversation) {69const intent = instantiationService.createInstance(AgentIntent);70const request = new TestChatRequest('');71request.command = 'compact';72const stream = new MockChatResponseStream();7374const chatTelemetry = instantiationService.createInstance(75ChatTelemetryBuilder,76Date.now(),77'sessionId',78undefined,79true,80request,81undefined,82);8384const result = await intent.handleRequest(85conversation,86request,87stream,88token,89undefined,90'agent',91ChatLocation.Agent,92chatTelemetry,93() => false94);9596return { result, stream };97}9899function createEditFileToolCall(idx: number): IToolCall {100return {101id: `tooluse_${idx}`,102name: ToolName.EditFile,103arguments: JSON.stringify({104filePath: '/workspace/file.ts',105code: `console.log('edit ${idx}')`106})107};108}109110function createEditFileToolResult(...idxs: number[]): Record<string, LanguageModelToolResult> {111const result: Record<string, LanguageModelToolResult> = {};112for (const idx of idxs) {113result[`tooluse_${idx}`] = new LanguageModelToolResult([new LanguageModelTextPart('success')]);114}115return result;116}117118function createConversationWithHistory(): Conversation {119// Create a previous turn with tool call rounds120const previousTurn = new Turn('turn1', { type: 'user', message: 'Create a file for me' });121const previousTurnResult: ICopilotChatResultIn = {122metadata: {123toolCallRounds: [124new ToolCallRound('Created the file', [createEditFileToolCall(1)], undefined, 'toolCallRoundId1'),125],126toolCallResults: createEditFileToolResult(1),127}128};129previousTurn.setResponse(TurnStatus.Success, { type: 'model', message: 'Done!' }, 'responseId1', previousTurnResult);130131// Create the current turn (the /summarize command turn)132const currentTurn = new Turn('turn2', { type: 'user', message: '/summarize' });133134return new Conversation('sessionId', [previousTurn, currentTurn]);135}136137function createConversationWithNoHistory(): Conversation {138// Just the current /summarize turn, no prior history139const currentTurn = new Turn('turn1', { type: 'user', message: '/summarize' });140return new Conversation('sessionId', [currentTurn]);141}142143test('returns summary metadata when enabled and history exists', async () => {144const conversation = createConversationWithHistory();145const { result } = await runSummarize(conversation);146147// Should have summary metadata148expect(result.metadata).toBeDefined();149expect(result.metadata?.summary).toBeDefined();150expect(result.metadata?.summary?.toolCallRoundId).toBe('toolCallRoundId1');151expect(result.metadata?.summary?.text).toBeTruthy();152});153154test('summarizes even when auto-summarize setting is disabled', async () => {155// Disable auto-summarization - /compact should still work since it's an explicit user action156configService.setConfig(ConfigKey.SummarizeAgentConversationHistory, false);157const conversation = createConversationWithHistory();158const { result } = await runSummarize(conversation);159160// Should still have summary metadata since /compact is explicit161expect(result.metadata).toBeDefined();162expect(result.metadata?.summary).toBeDefined();163expect(result.metadata?.summary?.toolCallRoundId).toBe('toolCallRoundId1');164expect(result.metadata?.summary?.text).toBeTruthy();165});166167test('returns friendly message when no history exists', async () => {168const conversation = createConversationWithNoHistory();169const { result, stream } = await runSummarize(conversation);170171// Should not have summary metadata172expect(result.metadata?.summary).toBeUndefined();173174// Should have output with "Nothing to compact" message175expect(stream.output.some(msg => msg.toLowerCase().includes('nothing to compact'))).toBe(true);176});177});178179180