// ── CLI Tool Name / Input Normalization ── const CLI_TOOL_NAME_MAP = { 'Edit': 'edit_file', 'Write': 'write_file', 'Read': 'read_file', 'Bash': 'execute', 'Glob': 'list_files', 'Grep': 'search_files', 'WebSearch': 'web_search', 'WebFetch': 'web_fetch', 'NotebookEdit': 'notebook_edit', 'ToolSearch': 'tool_search', 'mcp__gott-remote__remote_execute': 'remote_execute' }; /** Normalize CLI tool name to gott convention (Edit → edit_file, etc.) */ function normalizeToolName(name) { if (CLI_TOOL_NAME_MAP[name]) return CLI_TOOL_NAME_MAP[name]; // Generisch: mcp__server__tool → tool if (name && name.startsWith('mcp__')) { var parts = name.split('__'); return parts[parts.length - 1] || name; } return name; } /** Normalize CLI tool input fields (file_path → path, old_string → old_text, etc.) */ function normalizeToolInput(name, parsedInput) { if (!parsedInput || typeof parsedInput !== 'object') return parsedInput; var normalized = name === 'edit_file' || name === 'Edit'; var isWrite = name === 'write_file' || name === 'Write'; var isRead = name === 'read_file' || name === 'Read'; var isBash = name === 'execute' || name === 'Bash'; if (normalized) { return { path: parsedInput.path || parsedInput.file_path, old_text: parsedInput.old_text || parsedInput.old_string, new_text: parsedInput.new_text || parsedInput.new_string }; } if (isWrite || isRead) { var result = Object.assign({}, parsedInput); if (parsedInput.file_path && !parsedInput.path) result.path = parsedInput.file_path; return result; } if (isBash && parsedInput.command === undefined && parsedInput.description) { // CLI Bash tool hat command direkt return parsedInput; } return parsedInput; } // ── Tool Call Visibility ── const MODIFICATION_TOOLS = ['write_file', 'edit_file', 'delete_file', 'memory_write', 'remote_execute']; const TOOL_DISPLAY_NAMES = { list_files: 'Dateien auflisten', search_files: 'Dateien durchsuchen', read_file: 'Datei lesen', write_file: 'Datei schreiben', edit_file: 'Datei bearbeiten', delete_file: 'Datei löschen', glob: 'Dateien durchsuchen', grep: 'Dateien durchsuchen (Volltext)', memory_search: 'Erinnerungen durchsuchen', memory_read: 'Erinnerungen lesen', memory_write: 'Erinnerungen festhalten', web_fetch: 'Web-Fetch durchführen', web_search: 'Web durchsuchen', execute: 'Befehl ausführen', background_execute: 'Befehl ausführen (Hintergrund)', remote_execute: 'Befehl ausführen (Remote)', send_to_session: 'Nachricht an Session senden', tool_search: 'Tools durchsuchen', todo_write: 'Aufgabenliste aktualisieren', github_code_search: 'Code auf Github suchen', serper_web_search: 'Web per Serper durchsuchen', }; function isToolVisible(toolName) { const normalized = normalizeToolName(toolName); const mode = loadSettings().toolCallDisplay; if (mode === 'all') return true; if (mode === 'hidden') return false; // 'modifications' – nur modifizierende Tools + web_search anzeigen if (normalized === 'web_search') return true; return MODIFICATION_TOOLS.includes(normalized); } // ── Chain Meta Visibility ── function isChainMetaVisible(chainData) { const mode = loadSettings().chainMetaDisplay; if (mode === 'always') return true; if (mode === 'never') return false; // 'sometimes' – nur bei auffälligen Chains anzeigen if (!chainData) return false; const inOut = (chainData.inputTokens || 0) + (chainData.outputTokens || 0); if (inOut >= 10000) return true; if ((chainData.iterations || 0) >= 25) return true; if ((chainData.durationMs || 0) >= 90000) return true; return false; } // ── Tool Boxes ── function addToolBoxLoading(rawName) { const name = normalizeToolName(rawName); const div = document.createElement('div'); div.className = 'tool-box streamed'; div.dataset.toolName = name; const displayName = TOOL_DISPLAY_NAMES[name] || name; div.innerHTML = '' + esc(displayName) + ' '; chatEl().appendChild(div); scrollDown(); return div; } function renderToolBoxContent(div, rawName, inputJson, result, isError, sources) { const name = normalizeToolName(rawName); let parsedInput = null; try { parsedInput = normalizeToolInput(rawName, JSON.parse(inputJson)); } catch {} const header = document.createElement('div'); header.className = 'tool-box-header'; const nameSpan = document.createElement('span'); nameSpan.className = 'tool-name'; nameSpan.textContent = TOOL_DISPLAY_NAMES[name] || name; header.appendChild(nameSpan); if (isError) { const errSpan = document.createElement('span'); errSpan.className = 'tool-error-badge'; errSpan.textContent = 'error'; header.appendChild(errSpan); } div.innerHTML = ''; div.appendChild(header); let foldTarget = null; // Web-Search Sources – immer sichtbar, kein Toggle if (name === 'web_search' && sources && sources.length > 0) { const sourcesList = document.createElement('div'); sourcesList.className = 'web-search-sources'; sources.forEach(function(s) { const link = document.createElement('a'); link.href = s.url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.className = 'web-search-source'; var domain = ''; try { domain = new URL(s.url).hostname.replace('www.', ''); } catch(e) { domain = s.url; } link.innerHTML = ' ' + esc(domain) + '' + (s.title ? '' + esc(s.title) + '' : ''); sourcesList.appendChild(link); }); div.appendChild(sourcesList); } if (name === 'edit_file' && parsedInput && parsedInput.path) { const pathSpan = document.createElement('span'); pathSpan.className = 'tool-input'; pathSpan.textContent = parsedInput.path; header.appendChild(pathSpan); const diff = buildEditDiff(parsedInput.old_text || '', parsedInput.new_text || ''); diff.hidden = true; div.appendChild(diff); foldTarget = diff; } else if (name === 'write_file' && parsedInput && parsedInput.path) { const pathSpan = document.createElement('span'); pathSpan.className = 'tool-input'; pathSpan.textContent = parsedInput.path; header.appendChild(pathSpan); const content = parsedInput.content || ''; const lines = content.split('\n'); const lineCount = lines.length; const charCount = content.length; const meta = document.createElement('span'); meta.className = 'tool-write-meta'; meta.textContent = lineCount + ' Zeilen, ' + charCount + ' Zeichen'; header.appendChild(meta); const preview = buildWritePreview(content); preview.hidden = true; div.appendChild(preview); foldTarget = preview; } else if (name === 'todo_write') { // Todo-Liste: kein tool-input, standardmäßig sichtbar, custom parser if (result) { const todoList = buildTodoList(result, isError); div.appendChild(todoList); foldTarget = todoList; } } else { // Standard: path/command als input anzeigen let inputLabel = ''; if (parsedInput) { inputLabel = parsedInput.path || parsedInput.command || JSON.stringify(parsedInput).substring(0, 120); } else { inputLabel = truncate(inputJson, 120); } if (inputLabel) { const inputSpan = document.createElement('span'); inputSpan.className = 'tool-input'; inputSpan.textContent = inputLabel; header.appendChild(inputSpan); } if (result) { const resultDiv = document.createElement('div'); resultDiv.className = 'tool-result' + (isError ? ' tool-error' : ''); resultDiv.textContent = result; resultDiv.hidden = true; div.appendChild(resultDiv); foldTarget = resultDiv; } } if (foldTarget && name !== 'todo_write') { header.classList.add('clickable'); header.addEventListener('click', () => { foldTarget.hidden = !foldTarget.hidden; }); } } function _buildDiffLines(container, lines, type) { const cssClass = type === 'added' ? 'diff-added' : 'diff-removed'; const markerChar = type === 'added' ? '+' : '-'; for (const line of lines) { const row = document.createElement('div'); row.className = 'diff-line ' + cssClass; const marker = document.createElement('span'); marker.className = 'diff-marker'; marker.textContent = markerChar; const content = document.createElement('span'); content.className = 'diff-content'; content.textContent = line; row.appendChild(marker); row.appendChild(content); container.appendChild(row); } } function buildEditDiff(oldText, newText) { const container = document.createElement('div'); container.className = 'tool-diff'; _buildDiffLines(container, oldText.split('\n'), 'removed'); _buildDiffLines(container, newText.split('\n'), 'added'); return container; } function buildWritePreview(content) { const container = document.createElement('div'); container.className = 'tool-diff'; _buildDiffLines(container, content.split('\n'), 'added'); return container; } function buildTodoList(result, isError) { const container = document.createElement('div'); container.className = 'todo-list' + (isError ? ' tool-error' : ''); const lines = result.split('\n').map(l => l.replace(/\r$/, '')).filter(l => l.trim()); for (const line of lines) { // Format: ICON [id] [priority] content const m = line.match(/^(\S+)\s+\[([^\]]+)\]\s+(?:\[([^\]]+)\]\s+)?(.+)$/); const row = document.createElement('div'); row.className = 'todo-item'; if (m) { const icon = m[1]; // ✅ / 🔄 / ⬜ const id = m[2]; const prio = m[3] || null; const text = m[4]; const statusClass = icon === '✅' ? 'todo-done' : icon === '🔄' ? 'todo-progress' : 'todo-pending'; row.classList.add(statusClass); const iconSpan = document.createElement('span'); iconSpan.className = 'todo-icon'; iconSpan.textContent = icon; const idSpan = document.createElement('span'); idSpan.className = 'todo-id'; idSpan.textContent = id + '.'; const textSpan = document.createElement('span'); textSpan.className = 'todo-text'; textSpan.textContent = text; row.appendChild(iconSpan); row.appendChild(idSpan); row.appendChild(textSpan); if (prio) { const prioSpan = document.createElement('span'); prioSpan.className = 'todo-prio todo-prio-' + prio.toLowerCase(); prioSpan.textContent = prio; row.appendChild(prioSpan); } } else { row.textContent = line; } container.appendChild(row); } parseFluentEmojis(container); return container; } function finishToolBox(div, data) { if (!div) return; renderToolBoxContent(div, data.name, data.input, data.result, data.isError, data.sources); scrollDown(); } /** Fügt ein Tool-Result nachträglich an eine bestehende Tool-Box an (CLI tool_result Events). */ function appendToolResult(div, result, isError) { if (!div || !result) return; // Nur anfügen wenn noch kein Result vorhanden if (div.querySelector('.tool-result')) return; // Bei edit_file/write_file: Diff reicht, kein extra Result nötig var toolName = div.dataset.toolName; if (toolName === 'edit_file' || toolName === 'write_file') return; var rd = document.createElement('div'); rd.className = 'tool-result' + (isError ? ' tool-error' : ''); rd.textContent = truncate(result, 300); rd.hidden = true; div.appendChild(rd); // Header clickable machen falls noch nicht var hdr = div.querySelector('.tool-box-header'); if (hdr && !hdr.classList.contains('clickable')) { hdr.classList.add('clickable'); hdr.addEventListener('click', function() { rd.hidden = !rd.hidden; }); } }