Path: blob/main/extensions/copilot/src/extension/prompt/test/node/conversation.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*--------------------------------------------------------------------------------------------*/4/* eslint-disable local/code-no-unused-expressions */5import { describe, expect, it } from 'vitest';6import type { ChatResult } from 'vscode';7import { ChatVariablesCollection } from '../../common/chatVariablesCollection';8import { IResultMetadata, normalizeSummariesOnRounds, Turn, TurnMessage, TurnStatus } from '../../common/conversation';9import { ToolCallRound } from '../../common/toolCallRound';1011describe('Turn', () => {12describe('setResponse', () => {13it('should set the response message and status correctly', () => {14const request: TurnMessage = { type: 'user', message: 'Hello' };15const turn = new Turn('1', request, new ChatVariablesCollection([]));1617const result: ChatResult = { metadata: {} };18const response: TurnMessage = { type: 'model', message: 'Hi there!' };19turn.setResponse(TurnStatus.Success, response, undefined, result);2021expect(turn.responseMessage).to.equal(response);22expect(turn.responseStatus).to.equal(TurnStatus.Success);23expect(turn.responseChatResult === result);24});2526it('should throw an error if setResponse is called more than once', () => {27const request: TurnMessage = { type: 'user', message: 'Hello' };28const turn = new Turn('1', request, new ChatVariablesCollection([]));2930const response: TurnMessage = { type: 'model', message: 'Hi there!' };31turn.setResponse(TurnStatus.Success, response, undefined, undefined);3233expect(() => turn.setResponse(TurnStatus.Success, response, undefined, undefined)).to.throw();34});3536it('should default status to InProgress if not set', () => {37const request: TurnMessage = { type: 'user', message: 'Hello' };38const turn = new Turn('1', request, new ChatVariablesCollection([]));3940expect(turn.responseStatus).to.equal(TurnStatus.InProgress);41});4243const genericToolCall = { id: 'id', name: 'name', arguments: '{}' };44it('should restore summaries from metadata to current turns', () => {45const turn1 = new Turn('1', { type: 'user', message: 'Hello' });46const turn1Meta: Partial<IResultMetadata> = {47summary: {48text: 'summary 1',49toolCallRoundId: 'round1'50},51toolCallRounds: [52new ToolCallRound('Hello', [genericToolCall], undefined, 'round1'),53new ToolCallRound('Hello', [], undefined, 'round2'),54]55};56turn1.setResponse(TurnStatus.Success, { type: 'model', message: 'Hi there!' }, undefined, { metadata: turn1Meta });57normalizeSummariesOnRounds([turn1]);58expect(turn1.rounds[0].summary).to.equal('summary 1');59});6061it('should restore only the last summary from summaries array', () => {62const turn1 = new Turn('1', { type: 'user', message: 'Hello' });63const turn1Meta: Partial<IResultMetadata> = {64summaries: [65{ text: 'summary 1', toolCallRoundId: 'round1' },66{ text: 'summary 2', toolCallRoundId: 'round2' },67],68toolCallRounds: [69new ToolCallRound('Hello', [genericToolCall], undefined, 'round1'),70new ToolCallRound('Hello', [genericToolCall], undefined, 'round2'),71new ToolCallRound('Hello', [], undefined, 'round3'),72]73};74turn1.setResponse(TurnStatus.Success, { type: 'model', message: 'Hi there!' }, undefined, { metadata: turn1Meta });75normalizeSummariesOnRounds([turn1]);76expect(turn1.rounds[0].summary).to.be.undefined;77expect(turn1.rounds[1].summary).to.equal('summary 2');78});7980it('should restore only the last summary across turns', () => {81const turn1 = new Turn('1', { type: 'user', message: 'Hello' });82const turn1Meta: Partial<IResultMetadata> = {83toolCallRounds: [84new ToolCallRound('Hello', [genericToolCall], undefined, 'round1'),85new ToolCallRound('Hello', [genericToolCall], undefined, 'round2'),86]87};88turn1.setResponse(TurnStatus.Success, { type: 'model', message: 'Hi there!' }, undefined, { metadata: turn1Meta });8990const turn2 = new Turn('2', { type: 'user', message: 'Hello' });91const turn2Meta: Partial<IResultMetadata> = {92summaries: [93{ text: 'summary for round1', toolCallRoundId: 'round1' },94{ text: 'summary for round3', toolCallRoundId: 'round3' },95],96toolCallRounds: [97new ToolCallRound('Hello', [genericToolCall], undefined, 'round3'),98new ToolCallRound('Hello', [], undefined, 'round4'),99]100};101turn2.setResponse(TurnStatus.Success, { type: 'model', message: 'Hi there!' }, undefined, { metadata: turn2Meta });102103normalizeSummariesOnRounds([turn1, turn2]);104expect(turn1.rounds[0].summary).to.be.undefined;105expect(turn2.rounds[0].summary).to.equal('summary for round3');106});107108it('should restore summaries from pendingSummaries when resultMetadata is absent', () => {109// Simulates the mid-tool-call-loop case: setResponse hasn't been called yet,110// so resultMetadata is empty, but addPendingSummary stored the summary.111const turn1 = new Turn('1', { type: 'user', message: 'Hello' });112turn1.addPendingSummary('round1', 'pending summary text');113// No setResponse call — this is the key: resultMetadata doesn't exist114// Manually set rounds so normalizeSummariesOnRounds can find the target115const turn1Meta: Partial<IResultMetadata> = {116toolCallRounds: [117new ToolCallRound('Hello', [genericToolCall], undefined, 'round1'),118new ToolCallRound('Hello', [], undefined, 'round2'),119]120};121turn1.setResponse(TurnStatus.Success, { type: 'model', message: 'Hi there!' }, undefined, { metadata: turn1Meta });122// Clear resultMetadata summaries to simulate mid-loop state123(turn1.responseChatResult!.metadata as Record<string, unknown>)['summaries'] = undefined;124(turn1.responseChatResult!.metadata as Record<string, unknown>)['summary'] = undefined;125126normalizeSummariesOnRounds([turn1]);127expect(turn1.rounds[0].summary).to.equal('pending summary text');128});129130it('should restore summaries from metadata to previous turns', () => {131const turn1 = new Turn('1', { type: 'user', message: 'Hello' });132const turn1Meta: Partial<IResultMetadata> = {133toolCallRounds: [134new ToolCallRound('Hello', [genericToolCall], undefined, 'round1'),135new ToolCallRound('Hello', [], undefined, 'round2'),136]137};138turn1.setResponse(TurnStatus.Success, { type: 'model', message: 'Hi there!' }, undefined, { metadata: turn1Meta });139140const turn2 = new Turn('2', { type: 'user', message: 'Hello' });141const turn2Meta: Partial<IResultMetadata> = {142summary: {143text: 'summary',144toolCallRoundId: 'round1'145},146toolCallRounds: [147new ToolCallRound('Hello', [genericToolCall], undefined, 'round3'),148new ToolCallRound('Hello', [], undefined, 'round4'),149]150};151turn2.setResponse(TurnStatus.Success, { type: 'model', message: 'Hi there!' }, undefined, { metadata: turn2Meta });152153normalizeSummariesOnRounds([turn1, turn2]);154expect(turn1.rounds[0].summary).to.equal('summary');155expect(turn2.rounds[0].summary).to.equal(undefined);156});157});158});159160161