// ── 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',
'TodoWrite': 'todo_write',
'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: aus Input bauen (CLI hat nur generisches Result, API formatiert Result)
// Priorität: parsedInput.todos (immer vorhanden) > result (nur bei API-Pfad formatiert)
if (parsedInput && parsedInput.todos && Array.isArray(parsedInput.todos)) {
const todoList = buildTodoListFromInput(parsedInput.todos, isError);
div.appendChild(todoList);
foldTarget = todoList;
} else 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;
}
/** Baut Todo-Liste direkt aus dem Input-Array (für CLI-Pfad, wo Result nur generischer Text ist). */
function buildTodoListFromInput(todos, isError) {
const container = document.createElement('div');
container.className = 'todo-list' + (isError ? ' tool-error' : '');
for (var i = 0; i < todos.length; i++) {
const todo = todos[i];
const row = document.createElement('div');
row.className = 'todo-item';
const status = todo.status || 'pending';
const icon = status === 'completed' ? '✅' : status === 'in_progress' ? '🔄' : '⬜';
const statusClass = status === 'completed' ? 'todo-done' : status === 'in_progress' ? '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 = (todo.id || (i + 1)) + '.';
const textSpan = document.createElement('span');
textSpan.className = 'todo-text';
// CLI hat "activeForm" statt nur "content" – zeige content (Imperativ-Form)
textSpan.textContent = todo.content || todo.activeForm || '';
row.appendChild(iconSpan);
row.appendChild(idSpan);
row.appendChild(textSpan);
if (todo.priority) {
const prioSpan = document.createElement('span');
prioSpan.className = 'todo-prio todo-prio-' + todo.priority.toLowerCase();
prioSpan.textContent = todo.priority;
row.appendChild(prioSpan);
}
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;
var toolName = div.dataset.toolName;
// todo_write: Liste wurde bereits aus Input gebaut (in renderToolBoxContent)
if (toolName === 'todo_write') {
if (div.querySelector('.todo-list')) return; // Bereits vorhanden
// Fallback: versuche aus Result zu parsen (API-Pfad)
var todoList = buildTodoList(result, isError);
div.appendChild(todoList);
scrollDown();
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
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; });
}
}