agent-ecosystem/src/renderer/utils/toolRendering/toolSummaryHelpers.ts

292 lines
8 KiB
TypeScript

/**
* Tool Summary Helpers
*
* Utilities for generating human-readable summaries for tool calls.
*/
import { getBaseName } from '@renderer/utils/pathUtils';
import { summarizeAgentToolInput } from '@shared/utils/toolSummary';
/**
* Truncates a string to a maximum length with ellipsis.
*/
function truncate(str: string, maxLength: number): string {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength) + '...';
}
function readString(input: Record<string, unknown>, keys: string[]): string | undefined {
for (const key of keys) {
const value = input[key];
if (typeof value === 'string' && value.length > 0) {
return value;
}
}
return undefined;
}
/**
* Generates a human-readable summary for a tool call.
*/
export function getToolSummary(toolName: string, input: Record<string, unknown>): string {
const normalizedToolName = toolName.toLowerCase();
switch (normalizedToolName) {
case 'edit': {
const filePath = readString(input, ['file_path', 'filePath', 'path']);
const oldString = readString(input, ['old_string', 'oldString']);
const newString = readString(input, ['new_string', 'newString']);
if (!filePath) return 'Edit';
const fileName = getBaseName(filePath);
// Count line changes if we have old/new strings
if (oldString && newString) {
const oldLines = oldString.split('\n').length;
const newLines = newString.split('\n').length;
if (oldLines === newLines) {
return `${fileName} - ${oldLines} line${oldLines > 1 ? 's' : ''}`;
}
return `${fileName} - ${oldLines} -> ${newLines} lines`;
}
return fileName;
}
case 'read': {
const filePath = readString(input, ['file_path', 'filePath', 'path']);
const limit = input.limit as number | undefined;
const offset = input.offset as number | undefined;
if (!filePath) return 'Read';
const fileName = getBaseName(filePath);
if (limit) {
const start = offset ?? 1;
return `${fileName} - lines ${start}-${start + limit - 1}`;
}
return fileName;
}
case 'write': {
const filePath = readString(input, ['file_path', 'filePath', 'path']);
const content = readString(input, ['content', 'text']);
if (!filePath) {
return content
? `content - ${content.split('\n').length} lines`
: 'Write input unavailable';
}
const fileName = getBaseName(filePath);
if (content) {
const lineCount = content.split('\n').length;
return `${fileName} - ${lineCount} lines`;
}
return fileName;
}
case 'bash': {
const command = readString(input, ['command']);
const description = readString(input, ['description']);
// Prefer description if available
if (description) {
return truncate(description, 50);
}
if (command) {
return truncate(command, 50);
}
return 'Bash';
}
case 'grep': {
const pattern = readString(input, ['pattern']);
const path = readString(input, ['path']);
const glob = readString(input, ['glob']);
if (!pattern) return 'Grep';
const patternStr = `"${truncate(pattern, 30)}"`;
if (glob) {
return `${patternStr} in ${glob}`;
}
if (path) {
return `${patternStr} in ${getBaseName(path)}`;
}
return patternStr;
}
case 'glob': {
const pattern = readString(input, ['pattern']);
const path = readString(input, ['path']);
if (!pattern) return 'Glob';
const patternStr = `"${truncate(pattern, 30)}"`;
if (path) {
return `${patternStr} in ${getBaseName(path)}`;
}
return patternStr;
}
case 'task': {
const prompt = input.prompt as string | undefined;
const subagentType = input.subagentType as string | undefined;
const description = input.description as string | undefined;
const desc = description ?? prompt;
const typeStr = subagentType ? `${subagentType} - ` : '';
if (desc) {
return `${typeStr}${truncate(desc, 40)}`;
}
return subagentType ?? 'Task';
}
case 'lsp': {
const operation = input.operation as string | undefined;
const filePath = input.filePath as string | undefined;
if (!operation) return 'LSP';
if (filePath) {
return `${operation} - ${getBaseName(filePath)}`;
}
return operation;
}
case 'webfetch': {
const url = input.url as string | undefined;
if (url) {
try {
const urlObj = new URL(url);
return truncate(urlObj.hostname + urlObj.pathname, 50);
} catch {
return truncate(url, 50);
}
}
return 'WebFetch';
}
case 'websearch': {
const query = input.query as string | undefined;
if (query) {
return `"${truncate(query, 40)}"`;
}
return 'WebSearch';
}
case 'todowrite': {
const todos = input.todos as unknown[] | undefined;
if (todos && Array.isArray(todos)) {
return `${todos.length} item${todos.length !== 1 ? 's' : ''}`;
}
return 'TodoWrite';
}
case 'notebookedit': {
const notebookPath = input.notebook_path as string | undefined;
const editMode = input.edit_mode as string | undefined;
if (notebookPath) {
const fileName = getBaseName(notebookPath);
return editMode ? `${editMode} - ${fileName}` : fileName;
}
return 'NotebookEdit';
}
// =========================================================================
// Team Tools
// =========================================================================
case 'teamcreate': {
const teamName = input.team_name as string | undefined;
const desc = input.description as string | undefined;
if (teamName) return `${teamName}${desc ? ' - ' + truncate(desc, 30) : ''}`;
return 'Create team';
}
case 'taskcreate': {
const subject = input.subject as string | undefined;
return subject ? truncate(subject, 50) : 'Create task';
}
case 'taskupdate': {
const taskId = input.taskId as string | undefined;
const status = input.status as string | undefined;
const owner = input.owner as string | undefined;
const parts: string[] = [];
if (taskId) parts.push(`#${taskId}`);
if (status) parts.push(status);
if (owner) parts.push(`-> ${owner}`);
return parts.length > 0 ? parts.join(' ') : 'Update task';
}
case 'tasklist':
return 'List tasks';
case 'taskget': {
const taskId = input.taskId as string | undefined;
return taskId ? `Get task #${taskId}` : 'Get task';
}
case 'sendmessage': {
const msgType = input.type as string | undefined;
const recipient = input.recipient as string | undefined;
const summary = input.summary as string | undefined;
if (msgType === 'shutdown_request' && recipient) return `Shutdown ${recipient}`;
if (msgType === 'shutdown_response') return 'Shutdown response';
if (msgType === 'broadcast') return `Broadcast: ${truncate(summary ?? '', 30)}`;
if (recipient) return `To ${recipient}: ${truncate(summary ?? '', 30)}`;
return 'Send message';
}
case 'teamdelete':
return 'Delete team';
case 'agent': {
return summarizeAgentToolInput(input, 60);
}
default: {
// For unknown tools, try to extract a meaningful summary
const keys = Object.keys(input);
if (keys.length === 0) return toolName;
// Try common parameter names
const nameField = input.name ?? input.path ?? input.file ?? input.query ?? input.command;
if (typeof nameField === 'string') {
return truncate(nameField, 50);
}
// Fallback to showing first parameter
const firstValue = input[keys[0]];
if (typeof firstValue === 'string') {
return truncate(firstValue, 40);
}
return toolName;
}
}
}