/** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mapToDisplay } from './toolMapping.js'; import { type AnyDeclarativeTool, type AnyToolInvocation, type ToolCallRequestInfo, type ToolCallResponseInfo, type Status, type ToolCall, type ScheduledToolCall, type SuccessfulToolCall, type ExecutingToolCall, type WaitingToolCall, type CancelledToolCall, CoreToolCallStatus, } from '@google/gemini-cli-core'; import { ToolCallStatus, mapCoreStatusToDisplayStatus } from '../types.js'; describe('toolMapping', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('mapCoreStatusToDisplayStatus', () => { it.each([ [CoreToolCallStatus.Validating, ToolCallStatus.Pending], [CoreToolCallStatus.AwaitingApproval, ToolCallStatus.Confirming], [CoreToolCallStatus.Executing, ToolCallStatus.Executing], [CoreToolCallStatus.Success, ToolCallStatus.Success], [CoreToolCallStatus.Cancelled, ToolCallStatus.Canceled], [CoreToolCallStatus.Error, ToolCallStatus.Error], [CoreToolCallStatus.Scheduled, ToolCallStatus.Pending], ] as const)('maps %s to %s', (coreStatus, expectedDisplayStatus) => { expect(mapCoreStatusToDisplayStatus(coreStatus)).toBe( expectedDisplayStatus, ); }); it('throws error for unknown status due to checkExhaustive', () => { expect(() => mapCoreStatusToDisplayStatus('unknown_status' as Status), ).toThrow('unexpected value unknown_status!'); }); }); describe('mapToDisplay', () => { const mockRequest: ToolCallRequestInfo = { callId: 'call-1', name: 'test_tool', args: { arg1: 'val1' }, isClientInitiated: false, prompt_id: 'p1', }; const mockTool = { name: 'test_tool', displayName: 'Test Tool', isOutputMarkdown: true, } as unknown as AnyDeclarativeTool; const mockInvocation = { getDescription: () => 'Calling test_tool with args...', } as unknown as AnyToolInvocation; const mockResponse: ToolCallResponseInfo = { callId: 'call-1', responseParts: [], resultDisplay: 'Success output', error: undefined, errorType: undefined, }; it('handles a single tool call input', () => { const toolCall: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: mockRequest, tool: mockTool, invocation: mockInvocation, }; const result = mapToDisplay(toolCall); expect(result.type).toBe('tool_group'); expect(result.tools).toHaveLength(1); expect(result.tools[0]?.callId).toBe('call-1'); }); it('handles an array of tool calls', () => { const toolCall1: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: mockRequest, tool: mockTool, invocation: mockInvocation, }; const toolCall2: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: { ...mockRequest, callId: 'call-2' }, tool: mockTool, invocation: mockInvocation, }; const result = mapToDisplay([toolCall1, toolCall2]); expect(result.tools).toHaveLength(2); expect(result.tools[0]?.callId).toBe('call-1'); expect(result.tools[1]?.callId).toBe('call-2'); }); it('maps successful tool call properties correctly', () => { const toolCall: SuccessfulToolCall = { status: CoreToolCallStatus.Success, request: mockRequest, tool: mockTool, invocation: mockInvocation, response: { ...mockResponse, outputFile: '/tmp/output.txt', }, }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool).toEqual( expect.objectContaining({ callId: 'call-1', name: 'Test Tool', description: 'Calling test_tool with args...', renderOutputAsMarkdown: true, status: CoreToolCallStatus.Success, resultDisplay: 'Success output', outputFile: '/tmp/output.txt', }), ); }); it('maps executing tool call properties correctly with live output and ptyId', () => { const toolCall: ExecutingToolCall = { status: CoreToolCallStatus.Executing, request: mockRequest, tool: mockTool, invocation: mockInvocation, liveOutput: 'Loading...', pid: 12345, }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool.status).toBe(CoreToolCallStatus.Executing); expect(displayTool.resultDisplay).toBe('Loading...'); expect(displayTool.ptyId).toBe(12345); }); it('maps awaiting_approval tool call properties with correlationId', () => { const confirmationDetails = { type: 'exec' as const, title: 'Confirm Exec', command: 'ls', rootCommand: 'ls', rootCommands: ['ls'], onConfirm: vi.fn(), }; const toolCall: WaitingToolCall = { status: CoreToolCallStatus.AwaitingApproval, request: mockRequest, tool: mockTool, invocation: mockInvocation, confirmationDetails, correlationId: 'corr-id-123', }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool.status).toBe(CoreToolCallStatus.AwaitingApproval); expect(displayTool.confirmationDetails).toEqual(confirmationDetails); }); it('maps correlationId and serializable confirmation details', () => { const serializableDetails = { type: 'edit' as const, title: 'Confirm Edit', fileName: 'file.txt', filePath: '/path/file.txt', fileDiff: 'diff', originalContent: 'old', newContent: 'new', }; const toolCall: WaitingToolCall = { status: CoreToolCallStatus.AwaitingApproval, request: mockRequest, tool: mockTool, invocation: mockInvocation, confirmationDetails: serializableDetails, correlationId: 'corr-123', }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool.correlationId).toBe('corr-123'); expect(displayTool.confirmationDetails).toEqual(serializableDetails); }); it('maps error tool call missing tool definition', () => { // e.g. "TOOL_NOT_REGISTERED" errors const toolCall: ToolCall = { status: CoreToolCallStatus.Error, request: mockRequest, // name: 'test_tool' response: { ...mockResponse, resultDisplay: 'Tool not found' }, // notice: no `tool` or `invocation` defined here }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool.status).toBe(CoreToolCallStatus.Error); expect(displayTool.name).toBe('test_tool'); // falls back to request.name expect(displayTool.description).toBe('{"arg1":"val1"}'); // falls back to stringified args expect(displayTool.resultDisplay).toBe('Tool not found'); expect(displayTool.renderOutputAsMarkdown).toBe(false); }); it('maps cancelled tool call properties correctly', () => { const toolCall: CancelledToolCall = { status: CoreToolCallStatus.Cancelled, request: mockRequest, tool: mockTool, invocation: mockInvocation, response: { ...mockResponse, resultDisplay: 'User cancelled', // Could be diff output for edits }, }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool.status).toBe(CoreToolCallStatus.Cancelled); expect(displayTool.resultDisplay).toBe('User cancelled'); }); it('propagates borderTop and borderBottom options correctly', () => { const toolCall: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: mockRequest, tool: mockTool, invocation: mockInvocation, }; const result = mapToDisplay(toolCall, { borderTop: true, borderBottom: false, }); expect(result.borderTop).toBe(true); expect(result.borderBottom).toBe(false); }); it('maps raw progress and progressTotal from Executing calls', () => { const toolCall: ExecutingToolCall = { status: CoreToolCallStatus.Executing, request: mockRequest, tool: mockTool, invocation: mockInvocation, progressMessage: 'Downloading...', progress: 5, progressTotal: 10, }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool.progress).toBe(5); expect(displayTool.progressTotal).toBe(10); expect(displayTool.progressMessage).toBe('Downloading...'); }); it('leaves progress fields undefined for non-Executing calls', () => { const toolCall: SuccessfulToolCall = { status: CoreToolCallStatus.Success, request: mockRequest, tool: mockTool, invocation: mockInvocation, response: mockResponse, }; const result = mapToDisplay(toolCall); const displayTool = result.tools[0]; expect(displayTool.progress).toBeUndefined(); expect(displayTool.progressTotal).toBeUndefined(); }); it('sets resultDisplay to undefined for pre-execution statuses', () => { const toolCall: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: mockRequest, tool: mockTool, invocation: mockInvocation, }; const result = mapToDisplay(toolCall); expect(result.tools[0].resultDisplay).toBeUndefined(); expect(result.tools[0].status).toBe(CoreToolCallStatus.Scheduled); }); it('propagates originalRequestName correctly', () => { const toolCall: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: { ...mockRequest, originalRequestName: 'original_tool', }, tool: mockTool, invocation: mockInvocation, }; const result = mapToDisplay(toolCall); expect(result.tools[0].originalRequestName).toBe('original_tool'); }); it('propagates isClientInitiated from tool request', () => { const clientInitiatedTool: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: { ...mockRequest, callId: 'call-client', isClientInitiated: true, }, tool: mockTool, invocation: mockInvocation, }; const modelInitiatedTool: ScheduledToolCall = { status: CoreToolCallStatus.Scheduled, request: { ...mockRequest, callId: 'call-model', isClientInitiated: false, }, tool: mockTool, invocation: mockInvocation, }; const result = mapToDisplay([clientInitiatedTool, modelInitiatedTool]); expect(result.tools[0].isClientInitiated).toBe(true); expect(result.tools[1].isClientInitiated).toBe(false); }); }); });