Files
gemini-cli/packages/cli/src/ui/hooks/toolMapping.ts

137 lines
3.6 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
type ToolCall,
type Status as CoreStatus,
type ToolCallConfirmationDetails,
type SerializableConfirmationDetails,
type ToolResultDisplay,
debugLogger,
} from '@google/gemini-cli-core';
import {
ToolCallStatus,
type HistoryItemToolGroup,
type IndividualToolCallDisplay,
} from '../types.js';
import { checkExhaustive } from '@google/gemini-cli-core';
export function mapCoreStatusToDisplayStatus(
coreStatus: CoreStatus,
): ToolCallStatus {
switch (coreStatus) {
case 'validating':
return ToolCallStatus.Pending;
case 'awaiting_approval':
return ToolCallStatus.Confirming;
case 'executing':
return ToolCallStatus.Executing;
case 'success':
return ToolCallStatus.Success;
case 'cancelled':
return ToolCallStatus.Canceled;
case 'error':
return ToolCallStatus.Error;
case 'scheduled':
return ToolCallStatus.Pending;
default:
return checkExhaustive(coreStatus);
}
}
/**
* Transforms `ToolCall` objects into `HistoryItemToolGroup` objects for UI
* display. This is a pure projection layer and does not track interaction
* state.
*/
export function mapToDisplay(
toolOrTools: ToolCall[] | ToolCall,
options: { borderTop?: boolean; borderBottom?: boolean } = {},
): HistoryItemToolGroup {
const toolCalls = Array.isArray(toolOrTools) ? toolOrTools : [toolOrTools];
const { borderTop, borderBottom } = options;
const toolDisplays = toolCalls.map((call): IndividualToolCallDisplay => {
let description: string;
let renderOutputAsMarkdown = false;
const displayName = call.tool?.displayName ?? call.request.name;
if (call.status === 'error') {
description = JSON.stringify(call.request.args);
} else {
description = call.invocation.getDescription();
renderOutputAsMarkdown = call.tool.isOutputMarkdown;
}
const baseDisplayProperties = {
callId: call.request.callId,
name: displayName,
description,
renderOutputAsMarkdown,
};
let resultDisplay: ToolResultDisplay | undefined = undefined;
let confirmationDetails:
| ToolCallConfirmationDetails
| SerializableConfirmationDetails
| undefined = undefined;
let outputFile: string | undefined = undefined;
let ptyId: number | undefined = undefined;
let correlationId: string | undefined = undefined;
switch (call.status) {
case 'success':
resultDisplay = call.response.resultDisplay;
outputFile = call.response.outputFile;
break;
case 'error':
case 'cancelled':
resultDisplay = call.response.resultDisplay;
break;
case 'awaiting_approval':
correlationId = call.correlationId;
// Pass through details. Context handles dispatch (callback vs bus).
confirmationDetails = call.confirmationDetails;
break;
case 'executing':
resultDisplay = call.liveOutput;
ptyId = call.pid;
break;
case 'scheduled':
case 'validating':
break;
default: {
const exhaustiveCheck: never = call;
debugLogger.warn(
`Unhandled tool call status in mapper: ${
(exhaustiveCheck as ToolCall).status
}`,
);
break;
}
}
return {
...baseDisplayProperties,
status: mapCoreStatusToDisplayStatus(call.status),
resultDisplay,
confirmationDetails,
outputFile,
ptyId,
correlationId,
};
});
return {
type: 'tool_group',
tools: toolDisplays,
borderTop,
borderBottom,
};
}