/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { describe, it, expect } from 'vitest'; import { getResponseText, getResponseTextFromParts, getFunctionCalls, getFunctionCallsFromParts, getFunctionCallsAsJson, getFunctionCallsFromPartsAsJson, getStructuredResponse, getStructuredResponseFromParts, } from './generateContentResponseUtilities.js'; import { GenerateContentResponse, Part, FinishReason, SafetyRating, } from '@google/genai'; const mockTextPart = (text: string): Part => ({ text }); const mockFunctionCallPart = ( name: string, args?: Record, ): Part => ({ functionCall: { name, args: args ?? {} }, }); const mockResponse = ( parts: Part[], finishReason: FinishReason = FinishReason.STOP, safetyRatings: SafetyRating[] = [], ): GenerateContentResponse => ({ candidates: [ { content: { parts, role: 'model', }, index: 0, finishReason, safetyRatings, }, ], promptFeedback: { safetyRatings: [], }, text: undefined, data: undefined, functionCalls: undefined, executableCode: undefined, codeExecutionResult: undefined, }); const minimalMockResponse = ( candidates: GenerateContentResponse['candidates'], ): GenerateContentResponse => ({ candidates, promptFeedback: { safetyRatings: [] }, text: undefined, data: undefined, functionCalls: undefined, executableCode: undefined, codeExecutionResult: undefined, }); describe('generateContentResponseUtilities', () => { describe('getResponseText', () => { it('should return undefined for no candidates', () => { expect(getResponseText(minimalMockResponse(undefined))).toBeUndefined(); }); it('should return undefined for empty candidates array', () => { expect(getResponseText(minimalMockResponse([]))).toBeUndefined(); }); it('should return undefined for no parts', () => { const response = mockResponse([]); expect(getResponseText(response)).toBeUndefined(); }); it('should extract text from a single text part', () => { const response = mockResponse([mockTextPart('Hello')]); expect(getResponseText(response)).toBe('Hello'); }); it('should concatenate text from multiple text parts', () => { const response = mockResponse([ mockTextPart('Hello '), mockTextPart('World'), ]); expect(getResponseText(response)).toBe('Hello World'); }); it('should ignore function call parts', () => { const response = mockResponse([ mockTextPart('Hello '), mockFunctionCallPart('testFunc'), mockTextPart('World'), ]); expect(getResponseText(response)).toBe('Hello World'); }); it('should return undefined if only function call parts exist', () => { const response = mockResponse([ mockFunctionCallPart('testFunc'), mockFunctionCallPart('anotherFunc'), ]); expect(getResponseText(response)).toBeUndefined(); }); }); describe('getResponseTextFromParts', () => { it('should return undefined for no parts', () => { expect(getResponseTextFromParts([])).toBeUndefined(); }); it('should extract text from a single text part', () => { expect(getResponseTextFromParts([mockTextPart('Hello')])).toBe('Hello'); }); it('should concatenate text from multiple text parts', () => { expect( getResponseTextFromParts([ mockTextPart('Hello '), mockTextPart('World'), ]), ).toBe('Hello World'); }); it('should ignore function call parts', () => { expect( getResponseTextFromParts([ mockTextPart('Hello '), mockFunctionCallPart('testFunc'), mockTextPart('World'), ]), ).toBe('Hello World'); }); it('should return undefined if only function call parts exist', () => { expect( getResponseTextFromParts([ mockFunctionCallPart('testFunc'), mockFunctionCallPart('anotherFunc'), ]), ).toBeUndefined(); }); }); describe('getFunctionCalls', () => { it('should return undefined for no candidates', () => { expect(getFunctionCalls(minimalMockResponse(undefined))).toBeUndefined(); }); it('should return undefined for empty candidates array', () => { expect(getFunctionCalls(minimalMockResponse([]))).toBeUndefined(); }); it('should return undefined for no parts', () => { const response = mockResponse([]); expect(getFunctionCalls(response)).toBeUndefined(); }); it('should extract a single function call', () => { const func = { name: 'testFunc', args: { a: 1 } }; const response = mockResponse([ mockFunctionCallPart(func.name, func.args), ]); expect(getFunctionCalls(response)).toEqual([func]); }); it('should extract multiple function calls', () => { const func1 = { name: 'testFunc1', args: { a: 1 } }; const func2 = { name: 'testFunc2', args: { b: 2 } }; const response = mockResponse([ mockFunctionCallPart(func1.name, func1.args), mockFunctionCallPart(func2.name, func2.args), ]); expect(getFunctionCalls(response)).toEqual([func1, func2]); }); it('should ignore text parts', () => { const func = { name: 'testFunc', args: { a: 1 } }; const response = mockResponse([ mockTextPart('Some text'), mockFunctionCallPart(func.name, func.args), mockTextPart('More text'), ]); expect(getFunctionCalls(response)).toEqual([func]); }); it('should return undefined if only text parts exist', () => { const response = mockResponse([ mockTextPart('Some text'), mockTextPart('More text'), ]); expect(getFunctionCalls(response)).toBeUndefined(); }); }); describe('getFunctionCallsFromParts', () => { it('should return undefined for no parts', () => { expect(getFunctionCallsFromParts([])).toBeUndefined(); }); it('should extract a single function call', () => { const func = { name: 'testFunc', args: { a: 1 } }; expect( getFunctionCallsFromParts([mockFunctionCallPart(func.name, func.args)]), ).toEqual([func]); }); it('should extract multiple function calls', () => { const func1 = { name: 'testFunc1', args: { a: 1 } }; const func2 = { name: 'testFunc2', args: { b: 2 } }; expect( getFunctionCallsFromParts([ mockFunctionCallPart(func1.name, func1.args), mockFunctionCallPart(func2.name, func2.args), ]), ).toEqual([func1, func2]); }); it('should ignore text parts', () => { const func = { name: 'testFunc', args: { a: 1 } }; expect( getFunctionCallsFromParts([ mockTextPart('Some text'), mockFunctionCallPart(func.name, func.args), mockTextPart('More text'), ]), ).toEqual([func]); }); it('should return undefined if only text parts exist', () => { expect( getFunctionCallsFromParts([ mockTextPart('Some text'), mockTextPart('More text'), ]), ).toBeUndefined(); }); }); describe('getFunctionCallsAsJson', () => { it('should return JSON string of function calls', () => { const func1 = { name: 'testFunc1', args: { a: 1 } }; const func2 = { name: 'testFunc2', args: { b: 2 } }; const response = mockResponse([ mockFunctionCallPart(func1.name, func1.args), mockTextPart('text in between'), mockFunctionCallPart(func2.name, func2.args), ]); const expectedJson = JSON.stringify([func1, func2], null, 2); expect(getFunctionCallsAsJson(response)).toBe(expectedJson); }); it('should return undefined if no function calls', () => { const response = mockResponse([mockTextPart('Hello')]); expect(getFunctionCallsAsJson(response)).toBeUndefined(); }); }); describe('getFunctionCallsFromPartsAsJson', () => { it('should return JSON string of function calls from parts', () => { const func1 = { name: 'testFunc1', args: { a: 1 } }; const func2 = { name: 'testFunc2', args: { b: 2 } }; const parts = [ mockFunctionCallPart(func1.name, func1.args), mockTextPart('text in between'), mockFunctionCallPart(func2.name, func2.args), ]; const expectedJson = JSON.stringify([func1, func2], null, 2); expect(getFunctionCallsFromPartsAsJson(parts)).toBe(expectedJson); }); it('should return undefined if no function calls in parts', () => { const parts = [mockTextPart('Hello')]; expect(getFunctionCallsFromPartsAsJson(parts)).toBeUndefined(); }); }); describe('getStructuredResponse', () => { it('should return only text if only text exists', () => { const response = mockResponse([mockTextPart('Hello World')]); expect(getStructuredResponse(response)).toBe('Hello World'); }); it('should return only function call JSON if only function calls exist', () => { const func = { name: 'testFunc', args: { data: 'payload' } }; const response = mockResponse([ mockFunctionCallPart(func.name, func.args), ]); const expectedJson = JSON.stringify([func], null, 2); expect(getStructuredResponse(response)).toBe(expectedJson); }); it('should return text and function call JSON if both exist', () => { const text = 'Consider this data:'; const func = { name: 'processData', args: { item: 42 } }; const response = mockResponse([ mockTextPart(text), mockFunctionCallPart(func.name, func.args), ]); const expectedJson = JSON.stringify([func], null, 2); expect(getStructuredResponse(response)).toBe(`${text}\n${expectedJson}`); }); it('should return undefined if neither text nor function calls exist', () => { const response = mockResponse([]); expect(getStructuredResponse(response)).toBeUndefined(); }); }); describe('getStructuredResponseFromParts', () => { it('should return only text if only text exists in parts', () => { const parts = [mockTextPart('Hello World')]; expect(getStructuredResponseFromParts(parts)).toBe('Hello World'); }); it('should return only function call JSON if only function calls exist in parts', () => { const func = { name: 'testFunc', args: { data: 'payload' } }; const parts = [mockFunctionCallPart(func.name, func.args)]; const expectedJson = JSON.stringify([func], null, 2); expect(getStructuredResponseFromParts(parts)).toBe(expectedJson); }); it('should return text and function call JSON if both exist in parts', () => { const text = 'Consider this data:'; const func = { name: 'processData', args: { item: 42 } }; const parts = [ mockTextPart(text), mockFunctionCallPart(func.name, func.args), ]; const expectedJson = JSON.stringify([func], null, 2); expect(getStructuredResponseFromParts(parts)).toBe( `${text}\n${expectedJson}`, ); }); it('should return undefined if neither text nor function calls exist in parts', () => { const parts: Part[] = []; expect(getStructuredResponseFromParts(parts)).toBeUndefined(); }); }); });