fix(coding-agent): Coalesce adjacent assistant thinking blocks into one section

This commit is contained in:
Mario Zechner
2026-07-15 12:52:53 +02:00
parent fab309e955
commit 45203abfa0
3 changed files with 44 additions and 11 deletions
+2 -1
View File
@@ -34,11 +34,12 @@
### Fixed
- Fixed configured-provider catalog refresh to parse pi.dev's model-ID keyed responses, treat unimplemented routes as unavailable overlays, and show concise refresh status in `/model`.
- Fixed configured-provider catalog refresh to parse pi.dev's model-ID keyed responses, throttle checks to once per four hours, send the versioned pi user agent, treat unimplemented routes as unavailable overlays, and show concise refresh status in `/model`.
- Fixed inherited OpenRouter model context windows to use the top provider's actual context length ([#6481](https://github.com/earendil-works/pi-mono/pull/6481) by [@davidbrai](https://github.com/davidbrai)).
- Fixed inherited OpenRouter OpenAI-compatible session IDs to use the `x-session-id` header instead of OpenAI-specific session-affinity fields ([#6366](https://github.com/earendil-works/pi/issues/6366)).
- Fixed `Ctrl+V` to paste clipboard text when the pasteboard does not contain an image.
- Fixed `/login amazon-bedrock` to prompt for and save a Bedrock API key instead of only displaying ambient AWS credential setup instructions.
- Fixed adjacent assistant thinking blocks to render as one thinking section.
## [0.80.6] - 2026-07-09
@@ -101,7 +101,24 @@ export class AssistantMessageComponent extends Container {
// Assistant text messages with no background - trim the text
// Set paddingY=0 to avoid extra spacing before tool executions
this.contentContainer.addChild(new Markdown(content.text.trim(), this.outputPad, 0, this.markdownTheme));
} else if (content.type === "thinking" && content.thinking.trim()) {
} else if (content.type === "thinking") {
const thinkingBlocks: string[] = [];
for (; i < message.content.length; i++) {
const thinkingContent = message.content[i];
if (thinkingContent.type !== "thinking") {
break;
}
const thinking = thinkingContent.thinking.trim();
if (thinking) {
thinkingBlocks.push(thinking);
}
}
i--;
if (thinkingBlocks.length === 0) {
continue;
}
// Add spacing only when another visible assistant content block follows.
// This avoids a superfluous blank line before separately-rendered tool execution blocks.
const hasVisibleContentAfter = message.content
@@ -109,24 +126,21 @@ export class AssistantMessageComponent extends Container {
.some((c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()));
if (this.hideThinkingBlock) {
// Show static thinking label when hidden
// Show one static label for each run of thinking blocks when hidden.
this.contentContainer.addChild(
new Text(theme.italic(theme.fg("thinkingText", this.hiddenThinkingLabel)), this.outputPad, 0),
);
if (hasVisibleContentAfter) {
this.contentContainer.addChild(new Spacer(1));
}
} else {
// Thinking traces in thinkingText color, italic
// Render each run of thinking blocks as one Markdown section.
this.contentContainer.addChild(
new Markdown(content.thinking.trim(), this.outputPad, 0, this.markdownTheme, {
new Markdown(thinkingBlocks.join("\n\n"), this.outputPad, 0, this.markdownTheme, {
color: (text: string) => theme.fg("thinkingText", text),
italic: true,
}),
);
if (hasVisibleContentAfter) {
this.contentContainer.addChild(new Spacer(1));
}
}
if (hasVisibleContentAfter) {
this.contentContainer.addChild(new Spacer(1));
}
}
}
@@ -74,6 +74,24 @@ describe("AssistantMessageComponent", () => {
expect(rendered).toContain("response may be incomplete");
});
test("coalesces adjacent thinking blocks into one hidden thinking label", () => {
initTheme("dark");
const component = new AssistantMessageComponent(
createAssistantMessage([
{ type: "thinking", thinking: "first thought" },
{ type: "thinking", thinking: "" },
{ type: "thinking", thinking: "second thought" },
{ type: "text", text: "answer" },
]),
true,
);
const rendered = stripAnsi(component.render(80).join("\n"));
expect(rendered.match(/Thinking\.\.\./g)).toHaveLength(1);
expect(rendered).toContain("answer");
});
test("uses configured output padding for text and thinking", () => {
initTheme("dark");