diff --git a/proxy-patches/cli-to-openai.js b/proxy-patches/cli-to-openai.js index b944c5b..ddef6ae 100644 --- a/proxy-patches/cli-to-openai.js +++ b/proxy-patches/cli-to-openai.js @@ -99,7 +99,20 @@ function _parseToolCalls(text) { return { tool_calls: tcs, rest: restParts.join("").trim() }; } -export function cliResultToOpenai(result, requestId) { +// Laengere Texte, die Claude im Turn VOR der letzten Antwort geschrieben hat +// (z.B. ein Bericht, danach noch Warten/Werkzeuge, dann eine kurze +// Schlussnotiz). result.result enthaelt nur den LETZTEN Textblock — der +// Bericht ging so verloren (28.10., 2-Stunden-Turn). Kurze Zwischensaetze +// ("Ich schaue nach …") bleiben weg, Sprachausgabe nur aus der letzten Antwort. +const EARLIER_TEXT_MIN = 300; +function _mergeEarlier(finalText, earlier) { + const keep = (earlier || []) + .map(t => String(t || "").replace(/[\s\S]*?<\/voice>/g, "").replace(/\[\[[A-Z_]+\]\]/g, "").trim()) + .filter(t => t.length >= EARLIER_TEXT_MIN && !finalText.includes(t)); + return keep.length ? keep.join("\n\n") + "\n\n" + finalText : finalText; +} + +export function cliResultToOpenai(result, requestId, earlierTexts = []) { // modelUsage ist die Wahrheit: die CLI tragt dort den Namen ein, unter dem // der Request wirklich lief — bei einem Alias also die aufgeloeste Version // ("opus" -> "claude-opus-5-5"). Leer bleibt es, wenn gar kein Call @@ -121,7 +134,7 @@ export function cliResultToOpenai(result, requestId) { message.content = rest || null; finishReason = "tool_calls"; } else { - message.content = rawText; + message.content = _mergeEarlier(rawText, earlierTexts); } return { diff --git a/proxy-patches/routes.js b/proxy-patches/routes.js index 4b55ec7..6fa6ab3 100644 --- a/proxy-patches/routes.js +++ b/proxy-patches/routes.js @@ -130,7 +130,7 @@ function _emitStreamEvent(requestId, kind, fields) { // zugehoerigen Aufrufe ("Belege"). Nur start/tool_use/tool_result/end — // keine Text-Deltas. Fail-open: ein Schreibfehler stoert ARIA nie. const TOOL_LOG_DIR = process.env.ARIA_TOOL_LOG_DIR || "/shared/logs/aria-tools"; -const TOOL_LOG_KINDS = new Set(["start", "tool_use", "tool_result", "end"]); +const TOOL_LOG_KINDS = new Set(["start", "tool_use", "tool_result", "text", "end"]); const TOOL_LOG_KEEP_DAYS = 30; let _toolLogCleaned = ""; // Offensichtliche Geheimnisse nicht dauerhaft ablegen (Passwoerter, Tokens, @@ -149,7 +149,8 @@ function _redact(v) { function _persistToolEvent(ev) { if (!TOOL_LOG_KINDS.has(ev.kind)) return; try { - ev = { ...ev, input: _redact(ev.input), content: _redact(ev.content) }; + ev = { ...ev, input: _redact(ev.input), content: _redact(ev.content), + text: typeof ev.text === "string" ? _redact(ev.text).slice(0, 20000) : ev.text }; const day = new Date(ev.ts).toISOString().slice(0, 10); fs.mkdirSync(TOOL_LOG_DIR, { recursive: true }); fs.appendFile(`${TOOL_LOG_DIR}/${day}.jsonl`, JSON.stringify(ev) + "\n", () => {}); @@ -486,6 +487,16 @@ async function handleNonStreamingResponse(res, subprocess, cliInput, requestId) subprocess.on("result", (result) => { finalResult = result; }); + // Alle Textbloecke des Turns sammeln — die letzte Antwort allein + // (result.result) verliert sonst einen frueher geschriebenen Bericht. + const turnTexts = []; + subprocess.on("assistant", (message) => { + try { + for (const b of message?.message?.content || []) { + if (b && b.type === "text" && b.text) turnTexts.push(b.text); + } + } catch (_) { } + }); subprocess.on("error", (error) => { console.error("[NonStreaming] Error:", error.message); isComplete = true; @@ -508,7 +519,9 @@ async function handleNonStreamingResponse(res, subprocess, cliInput, requestId) return; } if (finalResult) { - res.json(cliResultToOpenai(finalResult, requestId)); + const last = String(finalResult.result || "").trim(); + const earlier = turnTexts.filter(t => t.trim() && t.trim() !== last); + res.json(cliResultToOpenai(finalResult, requestId, earlier)); } else if (!res.headersSent) { res.status(500).json({