Add thinking trace support and improve bash output formatting

- Render thinking traces in dark gray italic in final messages
- Show thinking as italic markdown in streaming view
- Remove code fences from bash output for cleaner minimal display
- Remove superfluous empty line after tool executions
- Thinking blocks now render inline in proper order with text
This commit is contained in:
Mario Zechner 2025-11-11 21:01:42 +01:00
parent 8fa780e052
commit ea5097e13e

View file

@ -66,13 +66,24 @@ class StreamingMessageComponent extends Container {
if (message.role === "assistant") { if (message.role === "assistant") {
const assistantMsg = message as AssistantMessage; const assistantMsg = message as AssistantMessage;
// Update text content // Update text and thinking content
const textContent = assistantMsg.content let combinedContent = "";
.filter((c) => c.type === "text") for (const c of assistantMsg.content) {
.map((c) => c.text) if (c.type === "text") {
.join(""); combinedContent += c.text;
} else if (c.type === "thinking") {
// Add thinking in italic
const thinkingLines = c.thinking
.split("\n")
.map((line) => `*${line}*`)
.join("\n");
if (combinedContent && !combinedContent.endsWith("\n")) combinedContent += "\n";
combinedContent += thinkingLines;
if (!combinedContent.endsWith("\n")) combinedContent += "\n";
}
}
this.markdown.setText(textContent); this.markdown.setText(combinedContent);
// Update usage stats // Update usage stats
const usage = assistantMsg.usage; const usage = assistantMsg.usage;
@ -140,16 +151,19 @@ class ToolExecutionComponent extends Container {
const command = this.args.command || ""; const command = this.args.command || "";
text = `**$ ${command}**`; text = `**$ ${command}**`;
if (this.result) { if (this.result) {
const lines = this.result.output.split("\n"); // Show output without code fences - more minimal
const output = this.result.output.trim();
if (output) {
const lines = output.split("\n");
const maxLines = 5; const maxLines = 5;
const displayLines = lines.slice(0, maxLines); const displayLines = lines.slice(0, maxLines);
const remaining = lines.length - maxLines; const remaining = lines.length - maxLines;
text += "\n```\n" + displayLines.join("\n"); text += "\n" + displayLines.join("\n");
if (remaining > 0) { if (remaining > 0) {
text += `\n... (${remaining} more lines)`; text += `\n... (${remaining} more lines)`;
} }
text += "\n```"; }
if (this.result.isError) { if (this.result.isError) {
text += " ❌"; text += " ❌";
@ -504,8 +518,6 @@ export class TuiRenderer {
output: typeof event.result === "string" ? event.result : event.result.output, output: typeof event.result === "string" ? event.result : event.result.output,
isError: event.isError, isError: event.isError,
}); });
// Add empty line after tool execution
this.chatContainer.addChild(new Text("", 0, 0));
this.pendingTools.delete(event.toolCallId); this.pendingTools.delete(event.toolCallId);
// Check if this was part of deferred stats and all tools are complete // Check if this was part of deferred stats and all tools are complete
@ -555,14 +567,19 @@ export class TuiRenderer {
} else if (message.role === "assistant") { } else if (message.role === "assistant") {
const assistantMsg = message as AssistantMessage; const assistantMsg = message as AssistantMessage;
// Render text content first (tool calls handled by events) // Render content in order
const textContent = assistantMsg.content for (const content of assistantMsg.content) {
.filter((c) => c.type === "text") if (content.type === "text" && content.text.trim()) {
.map((c) => c.text) // Assistant text messages with no background
.join(""); this.chatContainer.addChild(new Markdown(content.text));
if (textContent) { } else if (content.type === "thinking" && content.thinking.trim()) {
// Assistant messages with no background // Thinking traces in dark gray italic
this.chatContainer.addChild(new Markdown(textContent)); const thinkingText = content.thinking
.split("\n")
.map((line) => chalk.gray.italic(line))
.join("\n");
this.chatContainer.addChild(new Text(thinkingText, 1, 0));
}
} }
// Check if aborted - show after partial content // Check if aborted - show after partial content
@ -688,8 +705,6 @@ export class TuiRenderer {
isError: toolResultMsg.isError, isError: toolResultMsg.isError,
}); });
this.chatContainer.addChild(component); this.chatContainer.addChild(component);
// Add empty line after tool execution
this.chatContainer.addChild(new Text("", 0, 0));
// Check if this was the last tool call for this assistant message // Check if this was the last tool call for this assistant message
const assistantData = assistantWithTools.get(assistantMsgIndex); const assistantData = assistantWithTools.get(assistantMsgIndex);