diff options
| author | Jaana Dogan <[email protected]> | 2025-04-17 12:03:02 -0700 |
|---|---|---|
| committer | N. Taylor Mullen <[email protected]> | 2025-04-17 14:15:20 -0700 |
| commit | 81ba61df7f967f141cd8abc57d58a3612dfd4c2b (patch) | |
| tree | e4cf1a244c0b79fb7167105672c037954144783c /packages/cli/src/core/history-updater.ts | |
| parent | 898a83031c695f6da8705848ebe9998a7b626019 (diff) | |
Improve readability issues
This is only the first change of many changes.
* Remove redundant autogenerated comments
* Use the recommended file name style
* Use camelCase for variable names
* Don't introduce submodules for relevant types
* Don't introduce constants like modules, these are implementation details
* Remove empty files
Diffstat (limited to 'packages/cli/src/core/history-updater.ts')
| -rw-r--r-- | packages/cli/src/core/history-updater.ts | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/packages/cli/src/core/history-updater.ts b/packages/cli/src/core/history-updater.ts new file mode 100644 index 00000000..4013728f --- /dev/null +++ b/packages/cli/src/core/history-updater.ts @@ -0,0 +1,168 @@ +import { Part } from "@google/genai"; +import { toolRegistry } from "../tools/tool-registry.js"; +import { HistoryItem, IndividualToolCallDisplay, ToolCallEvent, ToolCallStatus, ToolConfirmationOutcome, ToolEditConfirmationDetails, ToolExecuteConfirmationDetails } from "../ui/types.js"; +import { ToolResultDisplay } from "../tools/tool.js"; + +/** + * Processes a tool call chunk and updates the history state accordingly. + * Manages adding new tool groups or updating existing ones. + * Resides here as its primary effect is updating history based on tool events. + */ +export const handleToolCallChunk = ( + chunk: ToolCallEvent, + setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>, + submitQuery: (query: Part) => Promise<void>, + getNextMessageId: () => number, + currentToolGroupIdRef: React.MutableRefObject<number | null> +): void => { + const toolDefinition = toolRegistry.getTool(chunk.name); + const description = toolDefinition?.getDescription + ? toolDefinition.getDescription(chunk.args) + : ''; + const toolDisplayName = toolDefinition?.displayName ?? chunk.name; + let confirmationDetails = chunk.confirmationDetails; + if (confirmationDetails) { + const originalConfirmationDetails = confirmationDetails; + const historyUpdatingConfirm = async (outcome: ToolConfirmationOutcome) => { + originalConfirmationDetails.onConfirm(outcome); + + if (outcome === ToolConfirmationOutcome.Cancel) { + let resultDisplay: ToolResultDisplay | undefined; + if ('fileDiff' in originalConfirmationDetails) { + resultDisplay = { fileDiff: (originalConfirmationDetails as ToolEditConfirmationDetails).fileDiff }; + } else { + resultDisplay = `~~${(originalConfirmationDetails as ToolExecuteConfirmationDetails).command}~~`; + } + handleToolCallChunk({ ...chunk, status: ToolCallStatus.Canceled, confirmationDetails: undefined, resultDisplay, }, setHistory, submitQuery, getNextMessageId, currentToolGroupIdRef); + const functionResponse: Part = { + functionResponse: { + name: chunk.name, + response: { "error": "User rejected function call." }, + }, + } + await submitQuery(functionResponse); + } else { + const tool = toolRegistry.getTool(chunk.name) + if (!tool) { + throw new Error(`Tool "${chunk.name}" not found or is not registered.`); + } + handleToolCallChunk({ ...chunk, status: ToolCallStatus.Invoked, resultDisplay: "Executing...", confirmationDetails: undefined }, setHistory, submitQuery, getNextMessageId, currentToolGroupIdRef); + const result = await tool.execute(chunk.args); + handleToolCallChunk({ ...chunk, status: ToolCallStatus.Invoked, resultDisplay: result.returnDisplay, confirmationDetails: undefined }, setHistory, submitQuery, getNextMessageId, currentToolGroupIdRef); + const functionResponse: Part = { + functionResponse: { + name: chunk.name, + id: chunk.callId, + response: { "output": result.llmContent }, + }, + } + await submitQuery(functionResponse); + } + } + + confirmationDetails = { + ...originalConfirmationDetails, + onConfirm: historyUpdatingConfirm, + }; + } + const toolDetail: IndividualToolCallDisplay = { + callId: chunk.callId, + name: toolDisplayName, + description, + resultDisplay: chunk.resultDisplay, + status: chunk.status, + confirmationDetails: confirmationDetails, + }; + + const activeGroupId = currentToolGroupIdRef.current; + setHistory(prev => { + if (chunk.status === ToolCallStatus.Pending) { + if (activeGroupId === null) { + // Start a new tool group + const newGroupId = getNextMessageId(); + currentToolGroupIdRef.current = newGroupId; + return [ + ...prev, + { id: newGroupId, type: 'tool_group', tools: [toolDetail] } as HistoryItem + ]; + } + + // Add to existing tool group + return prev.map(item => + item.id === activeGroupId && item.type === 'tool_group' + ? item.tools.some(t => t.callId === toolDetail.callId) + ? item // Tool already listed as pending + : { ...item, tools: [...item.tools, toolDetail] } + : item + ); + } + + // Update the status of a pending tool within the active group + if (activeGroupId === null) { + // Log if an invoked tool arrives without an active group context + console.warn("Received invoked tool status without an active tool group ID:", chunk); + return prev; + } + + return prev.map(item => + item.id === activeGroupId && item.type === 'tool_group' + ? { + ...item, + tools: item.tools.map(t => + t.callId === toolDetail.callId + ? { ...t, ...toolDetail, status: chunk.status } // Update details & status + : t + ) + } + : item + ); + }); +}; + +/** + * Appends an error or informational message to the history, attempting to attach + * it to the last non-user message or creating a new entry. + */ +export const addErrorMessageToHistory = ( + error: any, + setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>, + getNextMessageId: () => number +): void => { + const isAbort = error.name === 'AbortError'; + const errorType = isAbort ? 'info' : 'error'; + const errorText = isAbort + ? '[Request cancelled by user]' + : `[Error: ${error.message || 'Unknown error'}]`; + + setHistory(prev => { + const reversedHistory = [...prev].reverse(); + // Find the last message that isn't from the user to append the error/info to + const lastBotMessageIndex = reversedHistory.findIndex(item => item.type !== 'user'); + const originalIndex = lastBotMessageIndex !== -1 ? prev.length - 1 - lastBotMessageIndex : -1; + + if (originalIndex !== -1) { + // Append error to the last relevant message + return prev.map((item, index) => { + if (index === originalIndex) { + let baseText = ''; + // Determine base text based on item type + if (item.type === 'gemini') baseText = item.text ?? ''; + else if (item.type === 'tool_group') baseText = `Tool execution (${item.tools.length} calls)`; + else if (item.type === 'error' || item.type === 'info') baseText = item.text ?? ''; + // Safely handle potential undefined text + + const updatedText = (baseText + (baseText && !baseText.endsWith('\n') ? '\n' : '') + errorText).trim(); + // Reuse existing ID, update type and text + return { ...item, type: errorType, text: updatedText }; + } + return item; + }); + } else { + // No previous message to append to, add a new error item + return [ + ...prev, + { id: getNextMessageId(), type: errorType, text: errorText } as HistoryItem + ]; + } + }); +};
\ No newline at end of file |
