Path: blob/main/extensions/copilot/src/extension/chronicle/common/test/extractAssistantResponse.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 { describe, expect, it } from 'vitest';6import { extractAssistantResponse } from '../sessionStoreTracking';78describe('extractAssistantResponse', () => {9it('returns assistant text from valid JSON', () => {10const raw = JSON.stringify([11{ role: 'assistant', parts: [{ type: 'text', content: 'Hello, world!' }] },12]);1314expect(extractAssistantResponse(raw)).toBe('Hello, world!');15});1617it('returns undefined when input is undefined', () => {18expect(extractAssistantResponse(undefined)).toBeUndefined();19});2021it('returns undefined for empty string', () => {22expect(extractAssistantResponse('')).toBeUndefined();23});2425it('joins multiple text parts with newline', () => {26const raw = JSON.stringify([27{28role: 'assistant',29parts: [30{ type: 'text', content: 'Part one.' },31{ type: 'text', content: 'Part two.' },32],33},34]);3536expect(extractAssistantResponse(raw)).toBe('Part one.\nPart two.');37});3839it('ignores non-assistant roles', () => {40const raw = JSON.stringify([41{ role: 'system', parts: [{ type: 'text', content: 'System message' }] },42{ role: 'assistant', parts: [{ type: 'text', content: 'Assistant reply' }] },43]);4445expect(extractAssistantResponse(raw)).toBe('Assistant reply');46});4748it('ignores non-text parts (tool_call)', () => {49const raw = JSON.stringify([50{51role: 'assistant',52parts: [53{ type: 'tool_call', id: 'tc-1', name: 'some_tool', arguments: '{}' },54{ type: 'text', content: 'After tool call.' },55],56},57]);5859expect(extractAssistantResponse(raw)).toBe('After tool call.');60});6162it('returns undefined for empty parts array', () => {63const raw = JSON.stringify([64{ role: 'assistant', parts: [] },65]);6667expect(extractAssistantResponse(raw)).toBeUndefined();68});6970it('handles truncated JSON from truncateForOTel', () => {71// Simulate what truncateForOTel does: slices JSON mid-string and appends suffix72const longContent = 'A'.repeat(100_000);73const fullJson = JSON.stringify([74{ role: 'assistant', parts: [{ type: 'text', content: longContent }] },75]);76const truncated = fullJson.substring(0, 500) + '...[truncated, original 100123 chars]';7778const result = extractAssistantResponse(truncated);79expect(result).toBeDefined();80expect(result!.startsWith('AAAA')).toBe(true);81expect(result!).not.toContain('...[truncated');82});8384it('unescapes JSON escapes in truncated fallback path', () => {85// Content with characters that get JSON-escaped: newlines, quotes, backslashes86const content = 'Hello "world"\nLine two\tTabbed\\end';87const fullJson = JSON.stringify([88{ role: 'assistant', parts: [{ type: 'text', content: content + 'A'.repeat(100_000) }] },89]);90const truncated = fullJson.substring(0, 200) + '...[truncated, original 100050 chars]';9192const result = extractAssistantResponse(truncated);93expect(result).toBeDefined();94// Should be unescaped — actual newlines and quotes, not \\n and \\"95expect(result!).toContain('Hello "world"');96expect(result!).toContain('\n');97});9899it('returns undefined for malformed non-truncated JSON', () => {100expect(extractAssistantResponse('{not valid json at all}')).toBeUndefined();101});102103it('skips tool_call content and extracts text part in truncated JSON', () => {104// Simulate JSON with a tool_call part (which has a "content" field) before the text part105const fullJson = JSON.stringify([106{107role: 'assistant',108parts: [109{ type: 'tool_call', id: 'tc-1', content: 'should be skipped' },110{ type: 'text', content: 'The real answer' + 'B'.repeat(100_000) },111],112},113]);114const truncated = fullJson.substring(0, 500) + '...[truncated, original 100050 chars]';115116const result = extractAssistantResponse(truncated);117expect(result).toBeDefined();118expect(result!).toContain('The real answer');119expect(result!).not.toContain('should be skipped');120});121});122123124