File size: 4,109 Bytes
52efc7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {
  type ToolCall,
  type SerializableConfirmationDetails,
  type ToolResultDisplay,
  debugLogger,
  CoreToolCallStatus,
  type SubagentActivityItem,
} from '@google/gemini-cli-core';
import {
  type HistoryItemToolGroup,
  type IndividualToolCallDisplay,
} from '../types.js';

function hasSubagentHistory(
  call: ToolCall,
): call is ToolCall & { subagentHistory: SubagentActivityItem[] } {
  return 'subagentHistory' in call && call.subagentHistory !== undefined;
}

/**
 * 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;
    borderColor?: string;
    borderDimColor?: boolean;
  } = {},
): HistoryItemToolGroup {
  const toolCalls = Array.isArray(toolOrTools) ? toolOrTools : [toolOrTools];
  const { borderTop, borderBottom, borderColor, borderDimColor } = options;

  const toolDisplays = toolCalls.map((call): IndividualToolCallDisplay => {
    let description: string;
    let renderOutputAsMarkdown = false;

    const displayName = call.tool?.displayName ?? call.request.name;

    if (call.status === CoreToolCallStatus.Error) {
      description = JSON.stringify(call.request.args);
    } else {
      description = call.invocation.getDescription();
      renderOutputAsMarkdown = call.tool.isOutputMarkdown;
    }

    const baseDisplayProperties = {
      callId: call.request.callId,
      parentCallId: call.request.parentCallId,
      name: displayName,
      args: call.request.args,
      description,
      renderOutputAsMarkdown,
    };

    let resultDisplay: ToolResultDisplay | undefined = undefined;
    let confirmationDetails: SerializableConfirmationDetails | undefined =
      undefined;
    let outputFile: string | undefined = undefined;
    let ptyId: number | undefined = undefined;
    let correlationId: string | undefined = undefined;
    let progressMessage: string | undefined = undefined;
    let progress: number | undefined = undefined;
    let progressTotal: number | undefined = undefined;

    switch (call.status) {
      case CoreToolCallStatus.Success:
        resultDisplay = call.response.resultDisplay;
        outputFile = call.response.outputFile;
        break;
      case CoreToolCallStatus.Error:
      case CoreToolCallStatus.Cancelled:
        resultDisplay = call.response.resultDisplay;
        break;
      case CoreToolCallStatus.AwaitingApproval:
        correlationId = call.correlationId;
        // Pass through details. Context handles dispatch (callback vs bus).
        confirmationDetails = call.confirmationDetails;
        break;
      case CoreToolCallStatus.Executing:
        resultDisplay = call.liveOutput;
        ptyId = call.pid;
        progressMessage = call.progressMessage;
        progress = call.progress;
        progressTotal = call.progressTotal;
        break;
      case CoreToolCallStatus.Scheduled:
      case CoreToolCallStatus.Validating:
        break;
      default: {
        const exhaustiveCheck: never = call;
        debugLogger.warn(
          `Unhandled tool call status in mapper: ${
            (exhaustiveCheck as ToolCall).status
          }`,
        );
        break;
      }
    }

    return {
      ...baseDisplayProperties,
      status: call.status,
      isClientInitiated: !!call.request.isClientInitiated,
      kind: call.tool?.kind,
      resultDisplay,
      confirmationDetails,
      outputFile,
      ptyId,
      correlationId,
      progressMessage,
      progress,
      progressTotal,
      approvalMode: call.approvalMode,
      originalRequestName: call.request.originalRequestName,
      subagentHistory: hasSubagentHistory(call)
        ? call.subagentHistory
        : undefined,
    };
  });

  return {
    type: 'tool_group',
    tools: toolDisplays,
    borderTop,
    borderBottom,
    borderColor,
    borderDimColor,
  };
}