diff options
Diffstat (limited to 'packages/cli/src/core/history-updater.ts')
| -rw-r--r-- | packages/cli/src/core/history-updater.ts | 347 |
1 files changed, 207 insertions, 140 deletions
diff --git a/packages/cli/src/core/history-updater.ts b/packages/cli/src/core/history-updater.ts index 369454ff..12dd30c0 100644 --- a/packages/cli/src/core/history-updater.ts +++ b/packages/cli/src/core/history-updater.ts @@ -1,7 +1,15 @@ -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/tools.js"; +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/tools.js'; /** * Processes a tool call chunk and updates the history state accordingly. @@ -9,114 +17,160 @@ import { ToolResultDisplay } from "../tools/tools.js"; * 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> + 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); + 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); - } + if (outcome === ToolConfirmationOutcome.Cancel) { + let resultDisplay: ToolResultDisplay | undefined; + if ('fileDiff' in originalConfirmationDetails) { + resultDisplay = { + fileDiff: ( + originalConfirmationDetails as ToolEditConfirmationDetails + ).fileDiff, + }; + } else { + resultDisplay = `~~${(originalConfirmationDetails as ToolExecuteConfirmationDetails).command}~~`; } - - confirmationDetails = { - ...originalConfirmationDetails, - onConfirm: historyUpdatingConfirm, + 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.' }, + }, }; - } - const toolDetail: IndividualToolCallDisplay = { - callId: chunk.callId, - name: toolDisplayName, - description, - resultDisplay: chunk.resultDisplay, - status: chunk.status, - confirmationDetails: confirmationDetails, + 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 - ]; - } + 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 - ); - } + // 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; - } + // 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 - ); - }); + 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, + ); + }); }; /** @@ -124,45 +178,58 @@ export const handleToolCallChunk = ( * 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 + 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'}]`; + 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; + 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 + 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 - ]; + const updatedText = ( + baseText + + (baseText && !baseText.endsWith('\n') ? '\n' : '') + + errorText + ).trim(); + // Reuse existing ID, update type and text + return { ...item, type: errorType, text: updatedText }; } - }); -};
\ No newline at end of file + return item; + }); + } else { + // No previous message to append to, add a new error item + return [ + ...prev, + { + id: getNextMessageId(), + type: errorType, + text: errorText, + } as HistoryItem, + ]; + } + }); +}; |
