feat: CLI-Streaming – korrekte Tool-Darstellung, Reihenfolge und Error-Handling
- Tool-Namen/Inputs von CLI-Konvention auf gott-Konvention mappen (Edit→edit_file, file_path→path etc.) - Text vor Tool-Use in DB flushen für korrekte Reihenfolge (statt alles am Ende als Blob) - lineBuffer in ClaudeCliService vor Tool-Start und bei Text-Block-Ende flushen - ToolResult-Events parsen und in DB speichern (CLI sendet diese als type:"user" Messages) - Errors in DB speichern und als SSE-Event senden - Bild-Attachments als Temp-Dateien für CLI bereitstellen (Read-Tool kann sie lesen) - Frontend: tool_result Handler, Tool-Name-Normalisierung, appendToolResult() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
3e372b5a6e
commit
6d16d00290
4 files changed
+417
-17
No files matched your search
@@ -52,10 +52,18 @@ function handleStreamEvent(eventType, data) {
|
||||
case 'tool_end':
|
||||
if (_currentToolBox) {
|
||||
finishToolBox(_currentToolBox, data);
|
||||
_lastFinishedToolBox = _currentToolBox;
|
||||
}
|
||||
_currentToolBox = null;
|
||||
break;
|
||||
|
||||
case 'tool_result':
|
||||
if (_lastFinishedToolBox && data.result) {
|
||||
appendToolResult(_lastFinishedToolBox, data.result, data.isError);
|
||||
}
|
||||
_lastFinishedToolBox = null;
|
||||
break;
|
||||
|
||||
case 'web_search_start':
|
||||
pauseStreamBubble();
|
||||
_currentToolBox = addToolBoxLoading('web_search');
|
||||
@@ -158,6 +166,7 @@ function togglePlanMode() {
|
||||
let isStreaming = false;
|
||||
let _lastDoneChainData = null; // Chain-Daten vom letzten done-Event
|
||||
let _currentToolBox = null; // Aktuell laufende Tool-Box (für Stream-Events)
|
||||
let _lastFinishedToolBox = null; // Letzte fertige Tool-Box (für tool_result Zuordnung)
|
||||
let _chatAbandoned = false; // true wenn User weg-navigiert während Stream läuft
|
||||
let _abortController = null; // AbortController für laufenden Stream
|
||||
|
||||
|
||||
+73
-6
@@ -1,3 +1,50 @@
|
||||
// ── 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'
|
||||
};
|
||||
|
||||
/** Normalize CLI tool name to gott convention (Edit → edit_file, etc.) */
|
||||
function normalizeToolName(name) {
|
||||
return CLI_TOOL_NAME_MAP[name] || 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'];
|
||||
@@ -9,16 +56,18 @@ const TOOL_DISPLAY_NAMES = {
|
||||
web_search: 'Web-Suche',
|
||||
remote_execute: 'Remote-Befehl',
|
||||
memory_search: 'Erinnerungen durchsuchen',
|
||||
send_to_session: 'Nachricht an Session senden'
|
||||
send_to_session: 'Nachricht an Session senden',
|
||||
tool_search: 'Tool-Suche'
|
||||
};
|
||||
|
||||
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 (toolName === 'web_search') return true;
|
||||
return MODIFICATION_TOOLS.includes(toolName);
|
||||
if (normalized === 'web_search') return true;
|
||||
return MODIFICATION_TOOLS.includes(normalized);
|
||||
}
|
||||
|
||||
// ── Chain Meta Visibility ──
|
||||
@@ -38,7 +87,8 @@ function isChainMetaVisible(chainData) {
|
||||
|
||||
// ── Tool Boxes ──
|
||||
|
||||
function addToolBoxLoading(name) {
|
||||
function addToolBoxLoading(rawName) {
|
||||
const name = normalizeToolName(rawName);
|
||||
const div = document.createElement('div');
|
||||
div.className = 'tool-box streamed';
|
||||
div.dataset.toolName = name;
|
||||
@@ -49,9 +99,10 @@ function addToolBoxLoading(name) {
|
||||
return div;
|
||||
}
|
||||
|
||||
function renderToolBoxContent(div, name, inputJson, result, isError, sources) {
|
||||
function renderToolBoxContent(div, rawName, inputJson, result, isError, sources) {
|
||||
const name = normalizeToolName(rawName);
|
||||
let parsedInput = null;
|
||||
try { parsedInput = JSON.parse(inputJson); } catch {}
|
||||
try { parsedInput = normalizeToolInput(rawName, JSON.parse(inputJson)); } catch {}
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'tool-box-header';
|
||||
@@ -259,4 +310,20 @@ function finishToolBox(div, data) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user