refactor(core): Centralize tool response formatting (#743)
This commit is contained in:
parent
4b2af10b04
commit
d179b3aae4
|
@ -11,12 +11,7 @@ import {
|
|||
useReactToolScheduler,
|
||||
mapToDisplay,
|
||||
} from './useReactToolScheduler.js';
|
||||
import {
|
||||
Part,
|
||||
PartListUnion,
|
||||
PartUnion,
|
||||
FunctionResponse,
|
||||
} from '@google/genai';
|
||||
import { PartUnion, FunctionResponse } from '@google/genai';
|
||||
import {
|
||||
Config,
|
||||
ToolCallRequestInfo,
|
||||
|
@ -26,7 +21,6 @@ import {
|
|||
ToolCallConfirmationDetails,
|
||||
ToolConfirmationOutcome,
|
||||
ToolCallResponseInfo,
|
||||
formatLlmContentForFunctionResponse, // Import from core
|
||||
ToolCall, // Import from core
|
||||
Status as ToolCallStatusType,
|
||||
ApprovalMode, // Import from core
|
||||
|
@ -93,120 +87,6 @@ const mockToolRequiresConfirmation: Tool = {
|
|||
),
|
||||
};
|
||||
|
||||
describe('formatLlmContentForFunctionResponse', () => {
|
||||
it('should handle simple string llmContent', () => {
|
||||
const llmContent = 'Simple text output';
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({ output: 'Simple text output' });
|
||||
expect(additionalParts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as a single Part with text', () => {
|
||||
const llmContent: Part = { text: 'Text from Part object' };
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({ output: 'Text from Part object' });
|
||||
expect(additionalParts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as a PartListUnion array with a single text Part', () => {
|
||||
const llmContent: PartListUnion = [{ text: 'Text from array' }];
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({ output: 'Text from array' });
|
||||
expect(additionalParts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle llmContent with inlineData', () => {
|
||||
const llmContent: Part = {
|
||||
inlineData: { mimeType: 'image/png', data: 'base64...' },
|
||||
};
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({
|
||||
status: 'Binary content of type image/png was processed.',
|
||||
});
|
||||
expect(additionalParts).toEqual([llmContent]);
|
||||
});
|
||||
|
||||
it('should handle llmContent with fileData', () => {
|
||||
const llmContent: Part = {
|
||||
fileData: { mimeType: 'application/pdf', fileUri: 'gs://...' },
|
||||
};
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({
|
||||
status: 'Binary content of type application/pdf was processed.',
|
||||
});
|
||||
expect(additionalParts).toEqual([llmContent]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as an array of multiple Parts (text and inlineData)', () => {
|
||||
const llmContent: PartListUnion = [
|
||||
{ text: 'Some textual description' },
|
||||
{ inlineData: { mimeType: 'image/jpeg', data: 'base64data...' } },
|
||||
{ text: 'Another text part' },
|
||||
];
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({
|
||||
status: 'Tool execution succeeded.',
|
||||
});
|
||||
expect(additionalParts).toEqual(llmContent);
|
||||
});
|
||||
|
||||
it('should handle llmContent as an array with a single inlineData Part', () => {
|
||||
const llmContent: PartListUnion = [
|
||||
{ inlineData: { mimeType: 'image/gif', data: 'gifdata...' } },
|
||||
];
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({
|
||||
status: 'Binary content of type image/gif was processed.',
|
||||
});
|
||||
expect(additionalParts).toEqual(llmContent);
|
||||
});
|
||||
|
||||
it('should handle llmContent as a generic Part (not text, inlineData, or fileData)', () => {
|
||||
const llmContent: Part = { functionCall: { name: 'test', args: {} } };
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({
|
||||
status: 'Tool execution succeeded.',
|
||||
});
|
||||
expect(additionalParts).toEqual([llmContent]);
|
||||
});
|
||||
|
||||
it('should handle empty string llmContent', () => {
|
||||
const llmContent = '';
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({ output: '' });
|
||||
expect(additionalParts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as an empty array', () => {
|
||||
const llmContent: PartListUnion = [];
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({
|
||||
status: 'Tool execution succeeded.',
|
||||
});
|
||||
expect(additionalParts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as a Part with undefined inlineData/fileData/text', () => {
|
||||
const llmContent: Part = {}; // An empty part object
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(llmContent);
|
||||
expect(functionResponseJson).toEqual({
|
||||
status: 'Tool execution succeeded.',
|
||||
});
|
||||
expect(additionalParts).toEqual([llmContent]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useReactToolScheduler in YOLO Mode', () => {
|
||||
let onComplete: Mock;
|
||||
let setPendingHistoryItem: Mock;
|
||||
|
@ -289,13 +169,13 @@ describe('useReactToolScheduler in YOLO Mode', () => {
|
|||
request,
|
||||
response: expect.objectContaining({
|
||||
resultDisplay: 'YOLO Formatted tool output',
|
||||
responseParts: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
functionResponse: expect.objectContaining({
|
||||
responseParts: {
|
||||
functionResponse: {
|
||||
id: 'yoloCall',
|
||||
name: 'mockToolRequiresConfirmation',
|
||||
response: { output: expectedOutput },
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
@ -433,13 +313,13 @@ describe('useReactToolScheduler', () => {
|
|||
request,
|
||||
response: expect.objectContaining({
|
||||
resultDisplay: 'Formatted tool output',
|
||||
responseParts: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
functionResponse: expect.objectContaining({
|
||||
responseParts: {
|
||||
functionResponse: {
|
||||
id: 'call1',
|
||||
name: 'mockTool',
|
||||
response: { output: 'Tool output' },
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
@ -917,13 +797,13 @@ describe('useReactToolScheduler', () => {
|
|||
request: requests[0],
|
||||
response: expect.objectContaining({
|
||||
resultDisplay: 'Display 1',
|
||||
responseParts: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
functionResponse: expect.objectContaining({
|
||||
responseParts: {
|
||||
functionResponse: {
|
||||
id: 'multi1',
|
||||
name: 'tool1',
|
||||
response: { output: 'Output 1' },
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(call2Result).toMatchObject({
|
||||
|
@ -931,13 +811,13 @@ describe('useReactToolScheduler', () => {
|
|||
request: requests[1],
|
||||
response: expect.objectContaining({
|
||||
resultDisplay: 'Display 2',
|
||||
responseParts: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
functionResponse: expect.objectContaining({
|
||||
responseParts: {
|
||||
functionResponse: {
|
||||
id: 'multi2',
|
||||
name: 'tool2',
|
||||
response: { output: 'Output 2' },
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(result.current[0]).toEqual([]);
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { convertToFunctionResponse } from './coreToolScheduler.js';
|
||||
import { Part, PartListUnion } from '@google/genai';
|
||||
|
||||
describe('convertToFunctionResponse', () => {
|
||||
const toolName = 'testTool';
|
||||
const callId = 'call1';
|
||||
|
||||
it('should handle simple string llmContent', () => {
|
||||
const llmContent = 'Simple text output';
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual({
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: 'Simple text output' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle llmContent as a single Part with text', () => {
|
||||
const llmContent: Part = { text: 'Text from Part object' };
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual({
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: 'Text from Part object' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle llmContent as a PartListUnion array with a single text Part', () => {
|
||||
const llmContent: PartListUnion = [{ text: 'Text from array' }];
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual({
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: 'Text from array' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle llmContent with inlineData', () => {
|
||||
const llmContent: Part = {
|
||||
inlineData: { mimeType: 'image/png', data: 'base64...' },
|
||||
};
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: {
|
||||
output: 'Binary content of type image/png was processed.',
|
||||
},
|
||||
},
|
||||
},
|
||||
llmContent,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle llmContent with fileData', () => {
|
||||
const llmContent: Part = {
|
||||
fileData: { mimeType: 'application/pdf', fileUri: 'gs://...' },
|
||||
};
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: {
|
||||
output: 'Binary content of type application/pdf was processed.',
|
||||
},
|
||||
},
|
||||
},
|
||||
llmContent,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as an array of multiple Parts (text and inlineData)', () => {
|
||||
const llmContent: PartListUnion = [
|
||||
{ text: 'Some textual description' },
|
||||
{ inlineData: { mimeType: 'image/jpeg', data: 'base64data...' } },
|
||||
{ text: 'Another text part' },
|
||||
];
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: 'Tool execution succeeded.' },
|
||||
},
|
||||
},
|
||||
...llmContent,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as an array with a single inlineData Part', () => {
|
||||
const llmContent: PartListUnion = [
|
||||
{ inlineData: { mimeType: 'image/gif', data: 'gifdata...' } },
|
||||
];
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: {
|
||||
output: 'Binary content of type image/gif was processed.',
|
||||
},
|
||||
},
|
||||
},
|
||||
...llmContent,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as a generic Part (not text, inlineData, or fileData)', () => {
|
||||
const llmContent: Part = { functionCall: { name: 'test', args: {} } };
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual({
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: 'Tool execution succeeded.' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty string llmContent', () => {
|
||||
const llmContent = '';
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual({
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: '' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle llmContent as an empty array', () => {
|
||||
const llmContent: PartListUnion = [];
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: 'Tool execution succeeded.' },
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle llmContent as a Part with undefined inlineData/fileData/text', () => {
|
||||
const llmContent: Part = {}; // An empty part object
|
||||
const result = convertToFunctionResponse(toolName, callId, llmContent);
|
||||
expect(result).toEqual({
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: { output: 'Tool execution succeeded.' },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -14,7 +14,8 @@ import {
|
|||
ToolRegistry,
|
||||
ApprovalMode,
|
||||
} from '../index.js';
|
||||
import { Part, PartUnion, PartListUnion } from '@google/genai';
|
||||
import { Part, PartListUnion } from '@google/genai';
|
||||
import { getResponseTextFromParts } from '../utils/generateContentResponseUtilities.js';
|
||||
|
||||
export type ValidatingToolCall = {
|
||||
status: 'validating';
|
||||
|
@ -96,51 +97,79 @@ export type ToolCallsUpdateHandler = (toolCalls: ToolCall[]) => void;
|
|||
/**
|
||||
* Formats tool output for a Gemini FunctionResponse.
|
||||
*/
|
||||
export function formatLlmContentForFunctionResponse(
|
||||
llmContent: PartListUnion,
|
||||
): {
|
||||
functionResponseJson: Record<string, string>;
|
||||
additionalParts: PartUnion[];
|
||||
} {
|
||||
const additionalParts: PartUnion[] = [];
|
||||
let functionResponseJson: Record<string, string>;
|
||||
function createFunctionResponsePart(
|
||||
callId: string,
|
||||
toolName: string,
|
||||
output: string,
|
||||
): Part {
|
||||
return {
|
||||
functionResponse: {
|
||||
id: callId,
|
||||
name: toolName,
|
||||
response: { output },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function convertToFunctionResponse(
|
||||
toolName: string,
|
||||
callId: string,
|
||||
llmContent: PartListUnion,
|
||||
): PartListUnion {
|
||||
const contentToProcess =
|
||||
Array.isArray(llmContent) && llmContent.length === 1
|
||||
? llmContent[0]
|
||||
: llmContent;
|
||||
|
||||
if (typeof contentToProcess === 'string') {
|
||||
functionResponseJson = { output: contentToProcess };
|
||||
} else if (Array.isArray(contentToProcess)) {
|
||||
functionResponseJson = {
|
||||
status: 'Tool execution succeeded.',
|
||||
};
|
||||
additionalParts.push(...contentToProcess);
|
||||
} else if (contentToProcess.inlineData || contentToProcess.fileData) {
|
||||
return createFunctionResponsePart(callId, toolName, contentToProcess);
|
||||
}
|
||||
|
||||
if (Array.isArray(contentToProcess)) {
|
||||
const functionResponse = createFunctionResponsePart(
|
||||
callId,
|
||||
toolName,
|
||||
'Tool execution succeeded.',
|
||||
);
|
||||
return [functionResponse, ...contentToProcess];
|
||||
}
|
||||
|
||||
// After this point, contentToProcess is a single Part object.
|
||||
if (contentToProcess.functionResponse) {
|
||||
if (contentToProcess.functionResponse.response?.content) {
|
||||
const stringifiedOutput =
|
||||
getResponseTextFromParts(
|
||||
contentToProcess.functionResponse.response.content as Part[],
|
||||
) || '';
|
||||
return createFunctionResponsePart(callId, toolName, stringifiedOutput);
|
||||
}
|
||||
// It's a functionResponse that we should pass through as is.
|
||||
return contentToProcess;
|
||||
}
|
||||
|
||||
if (contentToProcess.inlineData || contentToProcess.fileData) {
|
||||
const mimeType =
|
||||
contentToProcess.inlineData?.mimeType ||
|
||||
contentToProcess.fileData?.mimeType ||
|
||||
'unknown';
|
||||
functionResponseJson = {
|
||||
status: `Binary content of type ${mimeType} was processed.`,
|
||||
};
|
||||
additionalParts.push(contentToProcess);
|
||||
} else if (contentToProcess.text !== undefined) {
|
||||
functionResponseJson = { output: contentToProcess.text };
|
||||
} else if (contentToProcess.functionResponse) {
|
||||
functionResponseJson = JSON.parse(
|
||||
JSON.stringify(contentToProcess.functionResponse),
|
||||
const functionResponse = createFunctionResponsePart(
|
||||
callId,
|
||||
toolName,
|
||||
`Binary content of type ${mimeType} was processed.`,
|
||||
);
|
||||
} else {
|
||||
functionResponseJson = { status: 'Tool execution succeeded.' };
|
||||
additionalParts.push(contentToProcess);
|
||||
return [functionResponse, contentToProcess];
|
||||
}
|
||||
|
||||
return {
|
||||
functionResponseJson,
|
||||
additionalParts,
|
||||
};
|
||||
if (contentToProcess.text !== undefined) {
|
||||
return createFunctionResponsePart(callId, toolName, contentToProcess.text);
|
||||
}
|
||||
|
||||
// Default case for other kinds of parts.
|
||||
return createFunctionResponsePart(
|
||||
callId,
|
||||
toolName,
|
||||
'Tool execution succeeded.',
|
||||
);
|
||||
}
|
||||
|
||||
const createErrorResponse = (
|
||||
|
@ -452,20 +481,15 @@ export class CoreToolScheduler {
|
|||
return;
|
||||
}
|
||||
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(toolResult.llmContent);
|
||||
|
||||
const functionResponsePart: Part = {
|
||||
functionResponse: {
|
||||
name: toolName,
|
||||
id: callId,
|
||||
response: functionResponseJson,
|
||||
},
|
||||
};
|
||||
const response = convertToFunctionResponse(
|
||||
toolName,
|
||||
callId,
|
||||
toolResult.llmContent,
|
||||
);
|
||||
|
||||
const successResponse: ToolCallResponseInfo = {
|
||||
callId,
|
||||
responseParts: [functionResponsePart, ...additionalParts],
|
||||
responseParts: response,
|
||||
resultDisplay: toolResult.returnDisplay,
|
||||
error: undefined,
|
||||
};
|
||||
|
|
|
@ -81,15 +81,13 @@ describe('executeToolCall', () => {
|
|||
expect(response.callId).toBe('call1');
|
||||
expect(response.error).toBeUndefined();
|
||||
expect(response.resultDisplay).toBe('Success!');
|
||||
expect(response.responseParts).toEqual([
|
||||
{
|
||||
expect(response.responseParts).toEqual({
|
||||
functionResponse: {
|
||||
name: 'testTool',
|
||||
id: 'call1',
|
||||
response: { output: 'Tool executed successfully' },
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if tool is not found', async () => {
|
||||
|
@ -225,7 +223,7 @@ describe('executeToolCall', () => {
|
|||
name: 'testTool',
|
||||
id: 'call5',
|
||||
response: {
|
||||
status: 'Binary content of type image/png was processed.',
|
||||
output: 'Binary content of type image/png was processed.',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Part } from '@google/genai';
|
||||
import {
|
||||
ToolCallRequestInfo,
|
||||
ToolCallResponseInfo,
|
||||
ToolRegistry,
|
||||
ToolResult,
|
||||
} from '../index.js';
|
||||
import { formatLlmContentForFunctionResponse } from './coreToolScheduler.js';
|
||||
import { convertToFunctionResponse } from './coreToolScheduler.js';
|
||||
|
||||
/**
|
||||
* Executes a single tool call non-interactively.
|
||||
|
@ -54,20 +53,15 @@ export async function executeToolCall(
|
|||
// No live output callback for non-interactive mode
|
||||
);
|
||||
|
||||
const { functionResponseJson, additionalParts } =
|
||||
formatLlmContentForFunctionResponse(toolResult.llmContent);
|
||||
|
||||
const functionResponsePart: Part = {
|
||||
functionResponse: {
|
||||
name: toolCallRequest.name,
|
||||
id: toolCallRequest.callId,
|
||||
response: functionResponseJson,
|
||||
},
|
||||
};
|
||||
const response = convertToFunctionResponse(
|
||||
toolCallRequest.name,
|
||||
toolCallRequest.callId,
|
||||
toolResult.llmContent,
|
||||
);
|
||||
|
||||
return {
|
||||
callId: toolCallRequest.callId,
|
||||
responseParts: [functionResponsePart, ...additionalParts],
|
||||
responseParts: response,
|
||||
resultDisplay: toolResult.returnDisplay,
|
||||
error: undefined,
|
||||
};
|
||||
|
|
|
@ -138,12 +138,7 @@ describe('DiscoveredMCPTool', () => {
|
|||
const stringifiedResponseContent = JSON.stringify(
|
||||
mockToolSuccessResultObject,
|
||||
);
|
||||
// getStringifiedResultForDisplay joins text parts, then wraps the array of processed parts in JSON
|
||||
const expectedDisplayOutput =
|
||||
'```json\n' +
|
||||
JSON.stringify([stringifiedResponseContent], null, 2) +
|
||||
'\n```';
|
||||
expect(toolResult.returnDisplay).toBe(expectedDisplayOutput);
|
||||
expect(toolResult.returnDisplay).toBe(stringifiedResponseContent);
|
||||
});
|
||||
|
||||
it('should handle empty result from getStringifiedResultForDisplay', async () => {
|
||||
|
|
|
@ -149,6 +149,13 @@ function getStringifiedResultForDisplay(result: Part[]) {
|
|||
return part; // Fallback for unexpected structure or non-FunctionResponsePart
|
||||
};
|
||||
|
||||
const processedResults = result.map(processFunctionResponse);
|
||||
const processedResults =
|
||||
result.length === 1
|
||||
? processFunctionResponse(result[0])
|
||||
: result.map(processFunctionResponse);
|
||||
if (typeof processedResults === 'string') {
|
||||
return processedResults;
|
||||
}
|
||||
|
||||
return '```json\n' + JSON.stringify(processedResults, null, 2) + '\n```';
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { GenerateContentResponse } from '@google/genai';
|
||||
import { GenerateContentResponse, Part } from '@google/genai';
|
||||
|
||||
export function getResponseText(
|
||||
response: GenerateContentResponse,
|
||||
|
@ -15,3 +15,7 @@ export function getResponseText(
|
|||
.join('') || undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function getResponseTextFromParts(parts: Part[]): string | undefined {
|
||||
return parts?.map((part) => part.text).join('') || undefined;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue