2025-08-26 02:48:19 +08:00
|
|
|
import { updateChat, updateMessage, updateMessageEntries } from '@buster/database';
|
2025-08-08 21:10:50 +08:00
|
|
|
import type { ToolCallOptions } from 'ai';
|
2025-08-15 03:49:34 +08:00
|
|
|
import type { UpdateMessageEntriesParams } from '../../../../../database/src/queries/messages/update-message-entries';
|
2025-08-08 06:09:45 +08:00
|
|
|
import type { DoneToolContext, DoneToolState } from './done-tool';
|
|
|
|
import {
|
|
|
|
createFileResponseMessages,
|
2025-08-26 02:48:19 +08:00
|
|
|
extractAllFilesForChatUpdate,
|
2025-08-08 06:09:45 +08:00
|
|
|
extractFilesFromToolCalls,
|
|
|
|
} from './helpers/done-tool-file-selection';
|
|
|
|
import {
|
|
|
|
createDoneToolRawLlmMessageEntry,
|
|
|
|
createDoneToolResponseMessage,
|
|
|
|
} from './helpers/done-tool-transform-helper';
|
2025-08-07 23:58:28 +08:00
|
|
|
|
|
|
|
// Factory function that creates a type-safe callback for the specific agent context
|
2025-08-16 04:48:18 +08:00
|
|
|
export function createDoneToolStart(context: DoneToolContext, doneToolState: DoneToolState) {
|
2025-08-08 11:38:43 +08:00
|
|
|
return async function doneToolStart(options: ToolCallOptions): Promise<void> {
|
2025-08-19 01:20:06 +08:00
|
|
|
// Reset state for new tool call to prevent contamination from previous calls
|
2025-08-15 04:39:51 +08:00
|
|
|
doneToolState.toolCallId = options.toolCallId;
|
2025-08-19 01:20:06 +08:00
|
|
|
doneToolState.args = undefined;
|
|
|
|
doneToolState.finalResponse = undefined;
|
2025-08-08 06:09:45 +08:00
|
|
|
|
|
|
|
// Extract files from the tool call responses in messages
|
|
|
|
if (options.messages) {
|
2025-08-14 04:19:32 +08:00
|
|
|
console.info('[done-tool-start] Extracting files from messages', {
|
|
|
|
messageCount: options.messages?.length,
|
|
|
|
toolCallId: options.toolCallId,
|
|
|
|
});
|
|
|
|
|
2025-08-26 02:48:19 +08:00
|
|
|
// Extract files for response messages (filtered to avoid duplicates)
|
2025-08-08 06:09:45 +08:00
|
|
|
const extractedFiles = extractFilesFromToolCalls(options.messages);
|
|
|
|
|
2025-08-26 02:48:19 +08:00
|
|
|
// Extract ALL files for updating the chat's most recent file (includes reports)
|
|
|
|
const allFilesForChatUpdate = extractAllFilesForChatUpdate(options.messages);
|
|
|
|
|
2025-08-14 04:19:32 +08:00
|
|
|
console.info('[done-tool-start] Files extracted', {
|
|
|
|
extractedCount: extractedFiles.length,
|
|
|
|
files: extractedFiles.map((f) => ({ id: f.id, type: f.fileType, name: f.fileName })),
|
2025-08-26 02:48:19 +08:00
|
|
|
allFilesCount: allFilesForChatUpdate.length,
|
|
|
|
allFiles: allFilesForChatUpdate.map((f) => ({
|
|
|
|
id: f.id,
|
|
|
|
type: f.fileType,
|
|
|
|
name: f.fileName,
|
|
|
|
})),
|
2025-08-14 04:19:32 +08:00
|
|
|
});
|
|
|
|
|
2025-08-26 02:48:19 +08:00
|
|
|
// Add extracted files as response messages (these are filtered to avoid duplicates)
|
2025-08-08 06:09:45 +08:00
|
|
|
if (extractedFiles.length > 0 && context.messageId) {
|
|
|
|
const fileResponses = createFileResponseMessages(extractedFiles);
|
|
|
|
|
2025-08-14 04:19:32 +08:00
|
|
|
console.info('[done-tool-start] Creating file response messages', {
|
|
|
|
responseCount: fileResponses.length,
|
|
|
|
});
|
|
|
|
|
2025-08-15 03:49:34 +08:00
|
|
|
// Add all files as response entries to the database in a single batch
|
|
|
|
try {
|
|
|
|
await updateMessageEntries({
|
|
|
|
messageId: context.messageId,
|
|
|
|
responseMessages: fileResponses,
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[done-tool] Failed to add file response entries:', error);
|
2025-08-08 06:09:45 +08:00
|
|
|
}
|
|
|
|
}
|
2025-08-26 02:48:19 +08:00
|
|
|
|
|
|
|
// Update the chat with the most recent file (using ALL files, including reports)
|
|
|
|
if (context.chatId && allFilesForChatUpdate.length > 0) {
|
|
|
|
// Sort files by version number (descending) to get the most recent
|
|
|
|
const sortedFiles = allFilesForChatUpdate.sort((a, b) => {
|
|
|
|
const versionA = a.versionNumber || 1;
|
|
|
|
const versionB = b.versionNumber || 1;
|
|
|
|
return versionB - versionA;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Prefer reports over other file types for the chat's most recent file
|
|
|
|
const reportFile = sortedFiles.find((f) => f.fileType === 'report');
|
|
|
|
const mostRecentFile = reportFile || sortedFiles[0];
|
|
|
|
|
|
|
|
if (mostRecentFile) {
|
|
|
|
console.info('[done-tool-start] Updating chat with most recent file', {
|
|
|
|
chatId: context.chatId,
|
|
|
|
fileId: mostRecentFile.id,
|
|
|
|
fileType: mostRecentFile.fileType,
|
|
|
|
fileName: mostRecentFile.fileName,
|
|
|
|
versionNumber: mostRecentFile.versionNumber,
|
|
|
|
isReport: mostRecentFile.fileType === 'report',
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
await updateChat(context.chatId, {
|
|
|
|
mostRecentFileId: mostRecentFile.id,
|
|
|
|
mostRecentFileType: mostRecentFile.fileType as 'metric' | 'dashboard' | 'report',
|
|
|
|
mostRecentVersionNumber: mostRecentFile.versionNumber || 1,
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[done-tool] Failed to update chat with most recent file:', error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-08-08 06:09:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const doneToolResponseEntry = createDoneToolResponseMessage(doneToolState, options.toolCallId);
|
|
|
|
const doneToolMessage = createDoneToolRawLlmMessageEntry(doneToolState, options.toolCallId);
|
|
|
|
|
2025-08-15 03:49:34 +08:00
|
|
|
const entries: UpdateMessageEntriesParams = {
|
|
|
|
messageId: context.messageId,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (doneToolResponseEntry) {
|
|
|
|
entries.responseMessages = [doneToolResponseEntry];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (doneToolMessage) {
|
|
|
|
entries.rawLlmMessages = [doneToolMessage];
|
|
|
|
}
|
|
|
|
|
2025-08-08 06:09:45 +08:00
|
|
|
try {
|
2025-08-15 03:49:34 +08:00
|
|
|
if (entries.responseMessages || entries.rawLlmMessages) {
|
|
|
|
await updateMessageEntries(entries);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark message as completed and add final reasoning message with workflow time
|
|
|
|
if (context.messageId) {
|
|
|
|
const currentTime = Date.now();
|
|
|
|
const elapsedTimeMs = currentTime - context.workflowStartTime;
|
|
|
|
const elapsedSeconds = Math.floor(elapsedTimeMs / 1000);
|
|
|
|
|
|
|
|
let timeString: string;
|
|
|
|
if (elapsedSeconds < 60) {
|
|
|
|
timeString = `${elapsedSeconds} seconds`;
|
|
|
|
} else {
|
|
|
|
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
2025-08-15 04:09:53 +08:00
|
|
|
timeString = `${elapsedMinutes} minutes`;
|
2025-08-15 03:49:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
await updateMessage(context.messageId, {
|
|
|
|
isCompleted: true,
|
2025-08-15 04:09:53 +08:00
|
|
|
finalReasoningMessage: `Reasoned for ${timeString}`,
|
2025-08-08 06:36:01 +08:00
|
|
|
});
|
2025-08-08 06:09:45 +08:00
|
|
|
}
|
|
|
|
} catch (error) {
|
2025-08-08 06:36:01 +08:00
|
|
|
console.error('[done-tool] Failed to update done tool raw LLM message:', error);
|
2025-08-08 06:09:45 +08:00
|
|
|
}
|
2025-08-07 23:58:28 +08:00
|
|
|
};
|
|
|
|
}
|