/** * @license * Copyright 2025 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import type { Config, SessionMetrics, TaskResultDisplay, ToolCallResponseInfo, } from '@qwen-code/qwen-code-core'; import { ToolErrorType, MCPServerStatus, getMCPServerStatus, OutputFormat, } from '@qwen-code/qwen-code-core'; import type { Part } from '@google/genai'; import type { CLIUserMessage, PermissionMode, } from '../nonInteractive/types.js'; import type { JsonOutputAdapterInterface } from '../nonInteractive/io/BaseJsonOutputAdapter.js'; import { normalizePartList, extractPartsFromUserMessage, extractUsageFromGeminiClient, computeUsageFromMetrics, buildSystemMessage, createTaskToolProgressHandler, functionResponsePartsToString, toolResultContent, } from './nonInteractiveHelpers.js'; // Mock dependencies vi.mock('../nonInteractiveCliCommands.js', () => ({ getAvailableCommands: vi .fn() .mockImplementation( async ( _config: unknown, _signal: AbortSignal, allowedBuiltinCommandNames?: string[], ) => { const allowedSet = new Set(allowedBuiltinCommandNames ?? []); const allCommands = [ { name: 'help', kind: 'built-in' }, { name: 'commit', kind: 'file' }, { name: 'memory', kind: 'built-in' }, { name: 'init', kind: 'built-in' }, { name: 'summary', kind: 'built-in' }, { name: 'compress', kind: 'built-in' }, ]; // Filter commands: always include file commands, only include allowed built-in commands return allCommands.filter( (cmd) => cmd.kind === 'file' || (cmd.kind === 'built-in' && allowedSet.has(cmd.name)), ); }, ), })); vi.mock('../ui/utils/computeStats.js', () => ({ computeSessionStats: vi.fn().mockReturnValue({ totalPromptTokens: 100, totalCachedTokens: 20, }), })); vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, getMCPServerStatus: vi.fn(), }; }); describe('normalizePartList', () => { it('should return empty array for null input', () => { expect(normalizePartList(null)).toEqual([]); }); it('should return empty array for undefined input', () => { expect(normalizePartList(undefined as unknown as null)).toEqual([]); }); it('should convert string to Part array', () => { const result = normalizePartList('test string'); expect(result).toEqual([{ text: 'test string' }]); }); it('should convert array of strings to Part array', () => { const result = normalizePartList(['hello', 'world']); expect(result).toEqual([{ text: 'hello' }, { text: 'world' }]); }); it('should convert array of mixed strings and Parts to Part array', () => { const part: Part = { text: 'existing' }; const result = normalizePartList(['new', part]); expect(result).toEqual([{ text: 'new' }, part]); }); it('should convert single Part object to array', () => { const part: Part = { text: 'single part' }; const result = normalizePartList(part); expect(result).toEqual([part]); }); it('should handle empty array', () => { expect(normalizePartList([])).toEqual([]); }); }); describe('extractPartsFromUserMessage', () => { it('should return null for undefined message', () => { expect(extractPartsFromUserMessage(undefined)).toBeNull(); }); it('should return null for null message', () => { expect( extractPartsFromUserMessage(null as unknown as undefined), ).toBeNull(); }); it('should extract string content', () => { const message: CLIUserMessage = { type: 'user', session_id: 'test-session', message: { role: 'user', content: 'test message', }, parent_tool_use_id: null, }; expect(extractPartsFromUserMessage(message)).toBe('test message'); }); it('should extract text blocks from content array', () => { const message: CLIUserMessage = { type: 'user', session_id: 'test-session', message: { role: 'user', content: [ { type: 'text', text: 'hello' }, { type: 'text', text: 'world' }, ], }, parent_tool_use_id: null, }; const result = extractPartsFromUserMessage(message); expect(result).toEqual([{ text: 'hello' }, { text: 'world' }]); }); it('should skip invalid blocks in content array', () => { const message: CLIUserMessage = { type: 'user', session_id: 'test-session', message: { role: 'user', content: [ { type: 'text', text: 'valid' }, null as unknown as { type: 'text'; text: string }, { type: 'text', text: 'also valid' }, ], }, parent_tool_use_id: null, }; const result = extractPartsFromUserMessage(message); expect(result).toEqual([{ text: 'valid' }, { text: 'also valid' }]); }); it('should convert non-text blocks to JSON strings', () => { const message: CLIUserMessage = { type: 'user', session_id: 'test-session', message: { role: 'user', content: [ { type: 'text', text: 'text block' }, { type: 'tool_use', id: '123', name: 'tool', input: {} }, ], }, parent_tool_use_id: null, }; const result = extractPartsFromUserMessage(message); expect(result).toEqual([ { text: 'text block' }, { text: JSON.stringify({ type: 'tool_use', id: '123', name: 'tool', input: {}, }), }, ]); }); it('should return null for empty content array', () => { const message: CLIUserMessage = { type: 'user', session_id: 'test-session', message: { role: 'user', content: [], }, parent_tool_use_id: null, }; expect(extractPartsFromUserMessage(message)).toBeNull(); }); it('should return null when message has no content', () => { const message: CLIUserMessage = { type: 'user', session_id: 'test-session', message: { role: 'user', content: undefined as unknown as string, }, parent_tool_use_id: null, }; expect(extractPartsFromUserMessage(message)).toBeNull(); }); }); describe('extractUsageFromGeminiClient', () => { it('should return undefined for null client', () => { expect(extractUsageFromGeminiClient(null)).toBeUndefined(); }); it('should return undefined for non-object client', () => { expect(extractUsageFromGeminiClient('not an object')).toBeUndefined(); }); it('should return undefined when getChat is not a function', () => { const client = { getChat: 'not a function' }; expect(extractUsageFromGeminiClient(client)).toBeUndefined(); }); it('should return undefined when chat does not have getDebugResponses', () => { const client = { getChat: vi.fn().mockReturnValue({}), }; expect(extractUsageFromGeminiClient(client)).toBeUndefined(); }); it('should extract usage from latest response with usageMetadata', () => { const client = { getChat: vi.fn().mockReturnValue({ getDebugResponses: vi.fn().mockReturnValue([ { usageMetadata: { promptTokenCount: 50 } }, { usageMetadata: { promptTokenCount: 100, candidatesTokenCount: 200, totalTokenCount: 300, cachedContentTokenCount: 10, }, }, ]), }), }; const result = extractUsageFromGeminiClient(client); expect(result).toEqual({ input_tokens: 100, output_tokens: 200, total_tokens: 300, cache_read_input_tokens: 10, }); }); it('should return default values when metadata values are not numbers', () => { const client = { getChat: vi.fn().mockReturnValue({ getDebugResponses: vi.fn().mockReturnValue([ { usageMetadata: { promptTokenCount: 'not a number', candidatesTokenCount: null, }, }, ]), }), }; const result = extractUsageFromGeminiClient(client); expect(result).toEqual({ input_tokens: 0, output_tokens: 0, }); }); it('should handle errors gracefully', () => { const client = { getChat: vi.fn().mockImplementation(() => { throw new Error('Test error'); }), }; const result = extractUsageFromGeminiClient(client); expect(result).toBeUndefined(); }); it('should skip responses without usageMetadata', () => { const client = { getChat: vi.fn().mockReturnValue({ getDebugResponses: vi.fn().mockReturnValue([ { someOtherData: 'value' }, { usageMetadata: { promptTokenCount: 50, candidatesTokenCount: 75, }, }, ]), }), }; const result = extractUsageFromGeminiClient(client); expect(result).toEqual({ input_tokens: 50, output_tokens: 75, }); }); }); describe('computeUsageFromMetrics', () => { it('should compute usage from SessionMetrics with single model', () => { const metrics: SessionMetrics = { models: { 'model-1': { api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 }, tokens: { prompt: 50, candidates: 100, total: 150, cached: 10, thoughts: 0, tool: 0, }, }, }, tools: { totalCalls: 0, totalSuccess: 0, totalFail: 0, totalDurationMs: 0, totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0, }, byName: {}, }, files: { totalLinesAdded: 0, totalLinesRemoved: 0, }, }; const result = computeUsageFromMetrics(metrics); expect(result).toEqual({ input_tokens: 100, output_tokens: 100, cache_read_input_tokens: 20, total_tokens: 150, }); }); it('should aggregate usage across multiple models', () => { const metrics: SessionMetrics = { models: { 'model-1': { api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 }, tokens: { prompt: 50, candidates: 100, total: 150, cached: 10, thoughts: 0, tool: 0, }, }, 'model-2': { api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 }, tokens: { prompt: 75, candidates: 125, total: 200, cached: 15, thoughts: 0, tool: 0, }, }, }, tools: { totalCalls: 0, totalSuccess: 0, totalFail: 0, totalDurationMs: 0, totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0, }, byName: {}, }, files: { totalLinesAdded: 0, totalLinesRemoved: 0, }, }; const result = computeUsageFromMetrics(metrics); expect(result).toEqual({ input_tokens: 100, output_tokens: 225, cache_read_input_tokens: 20, total_tokens: 350, }); }); it('should not include total_tokens when it is 0', () => { const metrics: SessionMetrics = { models: { 'model-1': { api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 }, tokens: { prompt: 50, candidates: 100, total: 0, cached: 10, thoughts: 0, tool: 0, }, }, }, tools: { totalCalls: 0, totalSuccess: 0, totalFail: 0, totalDurationMs: 0, totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0, }, byName: {}, }, files: { totalLinesAdded: 0, totalLinesRemoved: 0, }, }; const result = computeUsageFromMetrics(metrics); expect(result).not.toHaveProperty('total_tokens'); expect(result).toEqual({ input_tokens: 100, output_tokens: 100, cache_read_input_tokens: 20, }); }); it('should handle empty models', () => { const metrics: SessionMetrics = { models: {}, tools: { totalCalls: 0, totalSuccess: 0, totalFail: 0, totalDurationMs: 0, totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0, }, byName: {}, }, files: { totalLinesAdded: 0, totalLinesRemoved: 0, }, }; const result = computeUsageFromMetrics(metrics); expect(result).toEqual({ input_tokens: 100, output_tokens: 0, cache_read_input_tokens: 20, }); }); }); describe('buildSystemMessage', () => { let mockConfig: Config; beforeEach(() => { vi.clearAllMocks(); // Mock getMCPServerStatus to return CONNECTED by default vi.mocked(getMCPServerStatus).mockReturnValue(MCPServerStatus.CONNECTED); mockConfig = { getToolRegistry: vi.fn().mockReturnValue({ getAllToolNames: vi.fn().mockReturnValue(['tool1', 'tool2']), }), getMcpServers: vi.fn().mockReturnValue({ 'mcp-server-1': {}, 'mcp-server-2': {}, }), getTargetDir: vi.fn().mockReturnValue('/test/dir'), getModel: vi.fn().mockReturnValue('test-model'), getCliVersion: vi.fn().mockReturnValue('1.0.0'), getDebugMode: vi.fn().mockReturnValue(false), } as unknown as Config; }); it('should build system message with all fields', async () => { const allowedBuiltinCommands = ['init', 'summary', 'compress']; const result = await buildSystemMessage( mockConfig, 'test-session-id', 'auto' as PermissionMode, allowedBuiltinCommands, ); expect(result).toEqual({ type: 'system', subtype: 'init', uuid: 'test-session-id', session_id: 'test-session-id', cwd: '/test/dir', tools: ['tool1', 'tool2'], mcp_servers: [ { name: 'mcp-server-1', status: 'connected' }, { name: 'mcp-server-2', status: 'connected' }, ], model: 'test-model', permission_mode: 'auto', slash_commands: ['commit', 'compress', 'init', 'summary'], qwen_code_version: '1.0.0', agents: [], }); }); it('should handle empty tool registry', async () => { const config = { ...mockConfig, getToolRegistry: vi.fn().mockReturnValue(null), } as unknown as Config; const result = await buildSystemMessage( config, 'test-session-id', 'auto' as PermissionMode, ['init', 'summary'], ); expect(result.tools).toEqual([]); }); it('should handle empty MCP servers', async () => { const config = { ...mockConfig, getMcpServers: vi.fn().mockReturnValue(null), } as unknown as Config; const result = await buildSystemMessage( config, 'test-session-id', 'auto' as PermissionMode, ['init', 'summary'], ); expect(result.mcp_servers).toEqual([]); }); it('should use unknown version when getCliVersion returns null', async () => { const config = { ...mockConfig, getCliVersion: vi.fn().mockReturnValue(null), } as unknown as Config; const result = await buildSystemMessage( config, 'test-session-id', 'auto' as PermissionMode, ['init', 'summary'], ); expect(result.qwen_code_version).toBe('unknown'); }); it('should only include allowed built-in commands and all file commands', async () => { const allowedBuiltinCommands = ['init', 'summary']; const result = await buildSystemMessage( mockConfig, 'test-session-id', 'auto' as PermissionMode, allowedBuiltinCommands, ); // Should include: 'commit' (FILE), 'init' (BUILT_IN, allowed), 'summary' (BUILT_IN, allowed) // Should NOT include: 'help', 'memory', 'compress' (BUILT_IN but not in allowed set) expect(result.slash_commands).toEqual(['commit', 'init', 'summary']); }); it('should include only file commands when no built-in commands are allowed', async () => { const result = await buildSystemMessage( mockConfig, 'test-session-id', 'auto' as PermissionMode, [], // Empty array - no built-in commands allowed ); // Should only include 'commit' (FILE command) expect(result.slash_commands).toEqual(['commit']); }); }); describe('createTaskToolProgressHandler', () => { let mockAdapter: JsonOutputAdapterInterface; let mockConfig: Config; beforeEach(() => { mockConfig = { getDebugMode: vi.fn().mockReturnValue(false), isInteractive: vi.fn().mockReturnValue(false), getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), } as unknown as Config; mockAdapter = { processSubagentToolCall: vi.fn(), emitSubagentErrorResult: vi.fn(), emitToolResult: vi.fn(), emitUserMessage: vi.fn(), } as unknown as JsonOutputAdapterInterface; }); it('should create handler that processes task tool calls', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const taskDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: { arg1: 'value1' }, status: 'executing', }, ], }; handler('task-call-id', taskDisplay); expect(mockAdapter.processSubagentToolCall).toHaveBeenCalledWith( expect.objectContaining({ callId: 'tool-1', name: 'test_tool', status: 'executing', }), 'parent-tool-id', ); }); it('should emit tool_result when tool call completes', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const taskDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: { arg1: 'value1' }, status: 'success', resultDisplay: 'Success result', }, ], }; handler('task-call-id', taskDisplay); expect(mockAdapter.emitToolResult).toHaveBeenCalledWith( expect.objectContaining({ callId: 'tool-1', name: 'test_tool', }), expect.objectContaining({ callId: 'tool-1', resultDisplay: 'Success result', }), 'parent-tool-id', ); }); it('should not duplicate tool_use emissions', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const taskDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: {}, status: 'executing', }, ], }; // Call handler twice with same tool call handler('task-call-id', taskDisplay); handler('task-call-id', taskDisplay); expect(mockAdapter.processSubagentToolCall).toHaveBeenCalledTimes(1); }); it('should not duplicate tool_result emissions', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const taskDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: {}, status: 'success', resultDisplay: 'Result', }, ], }; // Call handler twice with same completed tool call handler('task-call-id', taskDisplay); handler('task-call-id', taskDisplay); expect(mockAdapter.emitToolResult).toHaveBeenCalledTimes(1); }); it('should handle status transitions from executing to completed', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); // First: executing state const executingDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: {}, status: 'executing', }, ], }; // Second: completed state const completedDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: {}, status: 'success', resultDisplay: 'Done', }, ], }; handler('task-call-id', executingDisplay); handler('task-call-id', completedDisplay); expect(mockAdapter.processSubagentToolCall).toHaveBeenCalledTimes(1); expect(mockAdapter.emitToolResult).toHaveBeenCalledTimes(1); }); it('should emit error result for failed task status', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const runningDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [], }; const failedDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'failed', terminateReason: 'Task failed with error', toolCalls: [], }; handler('task-call-id', runningDisplay); handler('task-call-id', failedDisplay); expect(mockAdapter.emitSubagentErrorResult).toHaveBeenCalledWith( 'Task failed with error', 0, 'parent-tool-id', ); }); it('should emit error result for cancelled task status', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const runningDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [], }; const cancelledDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'cancelled', toolCalls: [], }; handler('task-call-id', runningDisplay); handler('task-call-id', cancelledDisplay); expect(mockAdapter.emitSubagentErrorResult).toHaveBeenCalledWith( 'Task was cancelled', 0, 'parent-tool-id', ); }); it('should not process non-task-execution displays', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const nonTaskDisplay = { type: 'other', content: 'some content', }; handler('call-id', nonTaskDisplay as unknown as TaskResultDisplay); expect(mockAdapter.processSubagentToolCall).not.toHaveBeenCalled(); expect(mockAdapter.emitToolResult).not.toHaveBeenCalled(); }); it('should handle tool calls with failed status', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const taskDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: {}, status: 'failed', error: 'Tool execution failed', }, ], }; handler('task-call-id', taskDisplay); expect(mockAdapter.emitToolResult).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ callId: 'tool-1', error: expect.any(Error), errorType: ToolErrorType.EXECUTION_FAILED, }), 'parent-tool-id', ); }); it('should handle tool calls without result content', () => { const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', mockAdapter, ); const taskDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [ { callId: 'tool-1', name: 'test_tool', args: {}, status: 'success', resultDisplay: '', responseParts: [], }, ], }; handler('task-call-id', taskDisplay); // Should not emit tool_result if no content expect(mockAdapter.emitToolResult).not.toHaveBeenCalled(); }); it('should work with adapter that does not support subagent APIs', () => { const limitedAdapter = { emitToolResult: vi.fn(), } as unknown as JsonOutputAdapterInterface; const { handler } = createTaskToolProgressHandler( mockConfig, 'parent-tool-id', limitedAdapter, ); const taskDisplay: TaskResultDisplay = { type: 'task_execution', subagentName: 'test-agent', taskDescription: 'Test task', taskPrompt: 'Test prompt', status: 'running', toolCalls: [], }; // Should not throw expect(() => handler('task-call-id', taskDisplay)).not.toThrow(); }); }); describe('functionResponsePartsToString', () => { it('should extract output from functionResponse parts', () => { const parts: Part[] = [ { functionResponse: { response: { output: 'function output', }, }, }, ]; expect(functionResponsePartsToString(parts)).toBe('function output'); }); it('should handle multiple functionResponse parts', () => { const parts: Part[] = [ { functionResponse: { response: { output: 'output1', }, }, }, { functionResponse: { response: { output: 'output2', }, }, }, ]; expect(functionResponsePartsToString(parts)).toBe('output1output2'); }); it('should return empty string for missing output', () => { const parts: Part[] = [ { functionResponse: { response: {}, }, }, ]; expect(functionResponsePartsToString(parts)).toBe(''); }); it('should JSON.stringify non-functionResponse parts', () => { const parts: Part[] = [ { text: 'text part' }, { functionResponse: { response: { output: 'function output', }, }, }, ]; const result = functionResponsePartsToString(parts); expect(result).toContain('function output'); expect(result).toContain('text part'); }); it('should handle empty array', () => { expect(functionResponsePartsToString([])).toBe(''); }); it('should handle functionResponse with null response', () => { const parts: Part[] = [ { functionResponse: { response: null as unknown as Record, }, }, ]; expect(functionResponsePartsToString(parts)).toBe(''); }); }); describe('toolResultContent', () => { it('should return resultDisplay string when available', () => { const response: ToolCallResponseInfo = { callId: 'test-call', resultDisplay: 'Result content', responseParts: [], error: undefined, errorType: undefined, }; expect(toolResultContent(response)).toBe('Result content'); }); it('should return undefined for empty resultDisplay string', () => { const response: ToolCallResponseInfo = { callId: 'test-call', resultDisplay: ' ', responseParts: [], error: undefined, errorType: undefined, }; expect(toolResultContent(response)).toBeUndefined(); }); it('should use functionResponsePartsToString for responseParts', () => { const response: ToolCallResponseInfo = { callId: 'test-call', resultDisplay: undefined, responseParts: [ { functionResponse: { response: { output: 'function output', }, }, }, ], error: undefined, errorType: undefined, }; expect(toolResultContent(response)).toBe('function output'); }); it('should return error message when error is present', () => { const response: ToolCallResponseInfo = { callId: 'test-call', resultDisplay: undefined, responseParts: [], error: new Error('Test error message'), errorType: undefined, }; expect(toolResultContent(response)).toBe('Test error message'); }); it('should prefer resultDisplay over responseParts', () => { const response: ToolCallResponseInfo = { callId: 'test-call', resultDisplay: 'Direct result', responseParts: [ { functionResponse: { response: { output: 'function output', }, }, }, ], error: undefined, errorType: undefined, }; expect(toolResultContent(response)).toBe('Direct result'); }); it('should prefer responseParts over error', () => { const response: ToolCallResponseInfo = { callId: 'test-call', resultDisplay: undefined, error: new Error('Error message'), responseParts: [ { functionResponse: { response: { output: 'function output', }, }, }, ], errorType: undefined, }; expect(toolResultContent(response)).toBe('function output'); }); it('should return undefined when no content is available', () => { const response: ToolCallResponseInfo = { callId: 'test-call', resultDisplay: undefined, responseParts: [], error: undefined, errorType: undefined, }; expect(toolResultContent(response)).toBeUndefined(); }); });